/*
* (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
*
* 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.
*
* Contributors:
* Nuxeo - initial API and implementation
*
* $Id$
*/
package org.nuxeo.ecm.core.api.model;
import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
import org.nuxeo.common.utils.Path;
import org.nuxeo.ecm.core.api.Blob;
import org.nuxeo.ecm.core.api.PropertyException;
import org.nuxeo.ecm.core.api.model.resolver.PropertyObjectResolver;
import org.nuxeo.ecm.core.schema.types.ComplexType;
import org.nuxeo.ecm.core.schema.types.Field;
import org.nuxeo.ecm.core.schema.types.Schema;
import org.nuxeo.ecm.core.schema.types.Type;
/**
* Document properties are instances of document schema fields.
* <p>
* You can say that a {@link Field} object is like a Java class and a Property object like a class instance. Thus,
* schemas defines fields (or elements) which have a name and a type, and each field of a document can be instantiated
* (if the schema permits) as a Property object.
* <p>
* Properties are always bound to a schema field that provides the type and constraints on the property values. An
* exception is the root property the {@link DocumentPart} object which is not bound to a field but to a schema.
* <p>
* So properties are holding the actual values for each defined field.
* <p>
* The usual way of using properties is to get a document from the storage server then modify document properties and
* send them back to the storage server to that modifications are be stored.
* <p>
* Note that the storage server can be on a remote machine so when modifying properties remotely they are serialized and
* sent through the network between the two machines. This means properties must hold serializable values and also they
* must store some state flags so that the storage can decide which property was modified and how in order to correctly
* update the stored versions.
* <p>
* As we have seen each property may hold a serializable value which we will refer to as the <code>normalized</code>
* property value. For each schema field type there is only one java serializable object representation that will be
* used as the normalized value. The property API is giving you the possibility to use different compatible objects when
* setting or getting property values. Each property implementation will automatically convert the given value into a
* normalized one; so internally only the normalized value is stored.
* <p>
* For example, for date properties you may use either <code>Date</code> or <code>Calendar</code> when setting or
* retrieving a property value, but the normalized value will be the <code>Calendar</code> one.
* <p>
* As we have seen, properties keep some state flags. Property flags can be divided in two groups:
* <ul>
* <li>Dirty Flags - that reflect the public status of the document
* <li>Internal Flags - that reflect some internal state
* </ul>
* <p>
* Property Types:
* <p>
* Before going deeper in property flags, we will talk first about property types. There are several types of properties
* that are very closed on the type of fields they are bound onto.
* <ul>
* <li>Root Property (or <code>DocumentPart</code>) - this is a special property that is bound to a schema instead of a
* field And it is the root of the property tree.
* <li>Complex Properties - container properties that are bound to complex field types that can be represented as java
* <code>Map</code> objects. These properties contains a set of schema defined properties. You cannot add new child
* properties. You can only modify existing child properties. Complex property values are expressed as java
* <code>Map</code> objects.
* <ul>
* <li>Structured Properties - this is a special case of complex properties. The difference is that structured property
* values are expressed as <i>scalar</i> java objects instead of java maps. By scalar java objects we mean any well
* structured object which is not a container like a <code>Map</code> or a <code>Collection</code>. These objects are
* usually as scalar values - it doesn't make sense for example to set only some parts of that objects without creating
* the object completely. An example of usage are Blob properties that use {@link Blob} values.
* </ul>
* <li>List Properties - container properties that are bound to list field types.
* <li>Scalar Properties - atomic properties that are bound to scalar field types and that are using as values scalar or
* primitive java objects like arrays, primitives, String, Date etc.
* </ul>
* <p>
* As we've seen there are 2 categories of properties: container properties and scalar properties Complex and list
* properties are container properties while structured and scalar properties are scalar.
* <p>
* Dirty Flags:
* <p>
* Dirty flags are used to keep track of the dirty state of a property. The following flags are supported:
* <ul>
* <li> <code>IS_PHANTOM</code> - whether the property is existing in the storage (was explicitly set by the user) or it
* was dynamically generated using the default value by the implementation to fulfill schema definition. This applies to
* all property types
* <li> <code>IS_MODIFIED</code> - whether the property value was modified. This applies to all property types.
* <li> <code>IS_NEW</code> - whether the property is a new property that was added to a parent list property This
* applies only to properties that are children of a list property.
* <li> <code>IS_REMOVED</code> - whether a property was removed. A removed property will be removed from the storage and
* the next time you access the property it will be a <code>phantom</code> one. This applies only to properties that are
* children of a complex property.
* <li> <code>IS_MOVED</code> - whether the property was moved on another position inside the container list. This
* applies only to properties that are children of a list property.
* </ul>
* <p>
* There are several constraints on how property flags may change. This is a list of all changes that may occur over
* dirty flags:
* <ul>
* <li>NONE + MODIFIED => MODFIED
* <li>NONE + REMOVED => REMOVED
* <li>NONE + MOVED => MOVED
* <li>PHANTOM + MODIFIED => MODIFIED
* <li>NEW + MODIFIED => NEW | MODIFIED
* <li>NEW + MOVED => NEW | MOVED
* <li>MODIFIED + REMOVED => REMOVED
* <li>MODIFIED + MOVED => MODIFIED | MOVED
* <li>MODIFIED + MODIFIED => MODIFIED
* </ul>
* <p>
* The combinations not listed above are not permitted.
* <p>
* In case of list items, the REMOVED flag is not used since the property will be physically removed from the property
* tree.
* <p>
* Also when the dirty flag of a children property changes, its parent is informed to update its MODIFIED flag if
* needed. This way a modification on a children property is propagated to parents in the form of a MODIFIED flag.
* <p>
* Internal Flags:
* <p>
* Internal flags are used by the implementation to keep some internal state. For these flags you should look into the
* implementation
* <p>
* Apart flags properties can also hold some random user data using {@link Property#setData(Object)} and
* {@link Property#getData()} methods. This can be used for example to keep a context attached to a property. But be
* aware when using this you should provide serializable objects as the data you are attaching otherwise if properties
* are serialized / unserialized this will generate errors. The API is not forcing you to use serializable values since
* you can also use this feature to store temporary context data that will not be sent over the network.
*
* @see <code>TestPropertyModel</code> for usage of property API
* @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
*/
public interface Property extends Cloneable, Serializable, Iterable<Property> {
/**
* No dirty flags set.
*/
int NONE = 0;
/**
* Flag used to mark a property as new. Property was added to a list.
*/
int IS_NEW = 1;
/**
* Flag used to mark a property as dirty. Property value was modified.
*/
int IS_MODIFIED = 2;
/**
* Flag used to mark a property as dirty. Property was removed.
*/
int IS_REMOVED = 4;
/**
* Flag used to mark a property as dirty. Property was moved to another index.
*/
int IS_MOVED = 8;
/**
* Flag used to mark a property as phantom.
*/
int IS_PHANTOM = 16;
/**
* A mask for the first 4 flags: NEW, REMOVED, MODIFIED, MOVED.
*/
int IS_DIRTY = IS_NEW | IS_REMOVED | IS_MOVED | IS_MODIFIED;
/**
* A mask for public flags.
*/
int DIRTY_MASK = IS_PHANTOM | IS_DIRTY;
/**
* Tests if this property is new (just created but not yet stored).
* <p>
* A property is new when added to a collection. This is the typical state for a new property added to a list
*
* @return true if this property is new, false otherwise
*/
boolean isNew();
/**
* Tests if a property is flagged as removed. Removed properties are child property that were removed from their
* container.
*
* @return if the property was removed, false otherwise
*/
boolean isRemoved();
/**
* Tests if a property value was modified.
*
* @return if the property was removed, false otherwise
*/
boolean isModified();
/**
* Tests if a property value was moved to another index in the parent list if any.
*
* @return if the property was removed, false otherwise
*/
boolean isMoved();
/**
* Tests if the property is a phantom. This means it doesn't exists yet in the storage and it is not a new property.
* This is a placeholder for a property that is defined by the schema but was not yet set.
*
* @return true if a phantom false otherwise
*/
boolean isPhantom();
/**
* Tests whether a property is dirty.
* <p>
* This tests whether or not a dirty flag is set on the property.
*
* @return true if the property changed or is new
*/
boolean isDirty();
/**
* only for SimpleDocumentModel
*/
boolean isForceDirty();
/**
* only for SimpleDocumentModel
*/
void setForceDirty(boolean forceDirty);
/**
* Get the dirty flags that are set on this property.
*
* @return the dirty flags mask
*/
int getDirtyFlags();
/**
* Notify the property that its changes was stored so it can safely remove dirty flags.
* <p>
* Dirty flags are removed according to the type of the modifications. This way if the property was REMOVED it
* becomes a PHANTOM otherwise all dirty flags are cleared.
* <p>
* This method should be used by storage implementors to notify the property it should reset its dirty flags. Note
* that clearing dirty flags is not propagated to the parent property or to children. You need to clear dirty flags
* explicitly for each property.
*/
void clearDirtyFlags();
/**
* Whether the property is read only.
*
* @return true if read only false otherwise
*/
boolean isReadOnly();
/**
* Sets the read only flag.
*
* @param value true to set this property read only false otherwise
*/
void setReadOnly(boolean value);
/**
* Tests whether this property is of a map (complex) type.
*
* @return true if the property is of map type, false otherwise
*/
boolean isComplex();
/**
* Tests whether this property is of a list type.
*
* @return true if the property is of list type, false otherwise
*/
boolean isList();
/**
* Tests whether this property is of a scalar type.
*
* @return true if the property is of a scalar type, false otherwise
*/
boolean isScalar();
/**
* Whether this property is a container - this means the property value is a map or a list.
* <p>
* Container properties don't have a scalar values. Container values are computed each time they are requested - by
* calling on of the <code>getValue</code> methods - by collecting the values of the child properties.
*
* @return true if scalar false otherwise
*/
boolean isContainer();
/**
* Gets the property name.
*
* @return the property name
*/
String getName();
/**
* Gets the xpath of this property.
* <p>
* The xpath is of the form {@code pref:foo/mylist/123/elem}, of note:
* <ul>
* <li>there is no initial {@code /}
* <li>the schema prefix is only present if a prefix is configured for the base property's schema
* <li>list elements indexes start at 0
* <li>list elements aren't using the old syntax {@code foo/bar[123]/baz} but the new syntax {@code foo/123/baz}
* </ul>
*
* @return the xpath
* @since 9.1
*/
String getXPath();
/**
* Gets the path of this property relative to the owner document.
* <p>
* The path for top level properties is the same to the property name.
* <p>
* NOTE the path returned contains an initial {@code /} and expresses list indexes using an old syntax
*
* @return the path
* @deprecated since 9.1 as it's cumbersome to use; use {@link #getXPath} instead
*/
@Deprecated
String getPath();
/**
* Get the type of the field corresponding to this property.
*
* @return the property type
*/
Type getType();
/**
* Gets the field corresponding to this property.
* <p>
* The field is the object defining the property. You can see the field as a java class and the property as a class
* instance
*
* @return
*/
Field getField();
/**
* Gets the property parent.
*
* @return the property parent for sub properties or null for top level properties
*/
Property getParent();
/**
* Gets the document schema defining the property tree from which the property belongs.
*
* @return the document schema owning the field corresponding to the property
*/
Schema getSchema();
/**
* Gets the root property.
*
* @return the root property
*/
DocumentPart getRoot();
/**
* Initializes the property with the given normalized value.
* <p>
* The given value must be normalized - note that no check is done on that.
* <p>
* The phantom flag is unset by this operation.
* <p>
* This method should be used to initialize properties.
*
* @param value the normalized value to set
*/
void init(Serializable value) throws PropertyException;
/**
* Sets this property value. The value will be first normalized and then set.
* <p>
* For complex or list properties the value will be set recursively (as a map or list value).
*
* @param value the value to set
* @throws {@link InvalidPropertyValueException} if the given value type is not compatible with the expected value
* type
*/
void setValue(Object value) throws PropertyException;
/**
* Gets the property normalized value.
* <p>
* Normalized values are of the java type that correspond to the field type.
*
* @return the property value, which may be null
*/
Serializable getValue() throws PropertyException;
/**
* Gets the property normalized value for write.
* <p>
* Can be different fropm {@link #getValue()} in cases where the property adapts the value it is given to store.
*
* @return the property value to use for write, which may be null
* @since 5.2.1
*/
Serializable getValueForWrite() throws PropertyException;
/**
* Gets the property value as the given type.
* <p>
* The value is converted using the registered converter to the given type.
* <p>
* If conversion is not supported a runtime exception will be triggered.
*
* @return the property value, which may be null
*/
<T> T getValue(Class<T> type) throws PropertyException;
/**
* Removes this property from the tree.
* <p>
* This method marks the property as dirty and sets its value to null.
*
* @return the old property value
*/
Serializable remove() throws PropertyException;
/**
* Gets the child property having the given name.
* <p>
* If the property is a scalar, this will return always null.
* <p>
* The given name should be the full name (i.e. prefixed name if any prefix exists).
* <p>
* If a non prefixed name is given, the first child property having the given local name will be returned.
* <p>
* Relative paths are not resolved. THis method is intended to lookup direct children. For path lookups use
* {@link Property#resolvePath(String)} instead.
*
* @param name the child property name (the full name including the prefix if any)
* @return the child property if any null if no child property with that name is found or if the property is a
* scalar
* @throws {@link UnsupportedOperationException} if the property is a scalar property (doesn't have children)
* @throws {@link PropertyNotFoundException} if the child property is not found in the type definition
*/
Property get(String name) throws PropertyNotFoundException;
/**
* Get the child property given it's index. This operation is mandatory for List properties.
* <p>
* If this method is not supported an {@link UnsupportedOperationException} must be thrown
* <p>
* Relative paths are not resolved. THis method is intended to lookup direct chilren. For path lookups, use
* {@link Property#resolvePath(String)} instead.
*
* @param index
* @return the child property if any null if no child property with that name is found or if the property is a
* scalar
* @throws {@link UnsupportedOperationException} if the property is a scalar property (doesn't have children)
* @throws {@link PropertyNotFoundException} if the child property is not found in the type definition
*/
Property get(int index) throws PropertyNotFoundException;
/**
* Sets a child property value given its index. This method is required only for List properties.
* <p>
* If this method is not supported, an {@link UnsupportedOperationException} must be thrown.
* <p>
* This method will mark the child value as dirty for existing values and in the case of map properties it will mark
* phantom properties as new properties.
*
* @param index
* @param value the new value
* @throws {@link UnsupportedOperationException} if the property is a scalar property (doesn't have children)
* @throws {@link PropertyNotFoundException} if the child property is not found in the type definition
*/
void setValue(int index, Object value) throws PropertyException;
/**
* Get a collection over the children properties. This includes all children including phantom ones (those who are
* not yet set by the user).
* <p>
* The returned collection is ordered for list properties, and unordered for complex properties
* <p>
* Be aware that this method is creating phantom child properties for all schema fields that are not yet set.
*
* @return the children properties
*/
Collection<Property> getChildren();
/**
* Get the count of the children properties. This includes phantom properties. So the returned size will be equal to
* the one returned by the property {@link ComplexType#getFieldsCount()}.
*
* @return the children properties count
*/
int size();
/**
* Appends a new value to the list. A new property will be created to store the given value and appended to the
* children list.
* <p>
* The created property will be marked as {@link Property#isNew()}.
*
* @param value
* @return the added property
*/
Property addValue(Object value) throws PropertyException;
/**
* Inserts at the given position a new value to the list. A new property will be created to store the given value
* and appended to the children list.
* <p>
* The created property will be marked as {@link Property#isNew()}.
*
* @param value
* @param index the position to insert the value
* @return the added property
*/
Property addValue(int index, Object value) throws PropertyException;
/**
* Creates an empty child property and adds it as a property to the list container.
* <p>
* This method is useful to construct lists.
*
* @return the created property
* @throws PropertyException
*/
Property addEmpty() throws PropertyException;
/**
* Moves a property position into the parent container list.
* <p>
* This method applies only for list item properties. The given index includes removed properties.
*
* @param index the position in the parent container to move this property
* @throws UnsupportedOperationException if the operation is not supported by the target property
*/
void moveTo(int index);
/**
* Same as {@link Property#resolvePath(Path)} but with a string path as argument. This is the same as calling
* <code>resolvePath(new Path(path))</code>.
*
* @param path the string path to resolve.
* @return the resolved property
* @throws PropertyNotFoundException if the path cannot be resolved
*/
Property resolvePath(String path) throws PropertyNotFoundException;
/**
* Resolves the given path relative to the current property and return the property if any is found otherwise throws
* an exception.
* <p>
* The path format is a subset of XPath. Thus, / is used as path element separator, [n] for list element indexes.
* Attribute separator '@' are not supported since all properties are assumed to be elements. Also you .. and . can
* be used as element names.
* <p>
* Example of paths:
* <ul>
* <li><code>dc:title</code>
* <li><code>attachments/item[2]/mimeType</code>
* <li><code>../dc:title</code>
* </ul>
*
* @param path the path to resolve.
* @return the resolved property
* @throws PropertyNotFoundException if the path cannot be resolved
*/
Property resolvePath(Path path) throws PropertyNotFoundException;
/**
* Gets the value of the property resolved using the given path.
* <p>
* This method is a shortcut for: <code>resolvePath(path).getValue()</code>.
*
* @param path the path to the property
* @return the property value
*/
Serializable getValue(String path) throws PropertyException;
/**
* Gets the value of the property resolved using the given path.
* <p>
* The value will be converted to the given type if possible, otherwise an exception will be thrown.
* <p>
* This method is a shortcut for: <code>resolvePath(path).getValue(type)</code>.
*
* @param <T> The type of the value to return
* @param type the class of the value
* @param path the java path of the property value
* @return the value
* @throws PropertyException
*/
<T> T getValue(Class<T> type, String path) throws PropertyException;
/**
* Sets the value of the property resolved using the given path.
* <p>
* This method is a shortcut for: <code>resolvePath(path).setValue(value)</code>.
*
* @param path the property path
* @param value the value
* @throws PropertyException
*/
void setValue(String path, Object value) throws PropertyException;
/**
* Normalizes the given value as dictated by the property type.
* <p>
* Normalized values are the ones that are used for transportation over the net and that are given to the storage
* implementation to be stored in the repository
* <p>
* Normalized values must be {@link Serializable}
* <p>
* If the given value is already normalized it will be returned back.
*
* @param value the value to normalize according to the property type
* @return the normalized value
*/
Serializable normalize(Object value) throws PropertyConversionException;
/**
* Checks if the given value is a normalized one. This means the value has a type that is normalized.
* <p>
* Null values are considered as normalized.
*
* @param value the value to check
* @return true if the value is normalized false otherwise
*/
boolean isNormalized(Object value);
/**
* Converts the given normalized value to the given type.
* <p>
* If the value has already the given type it will be returned back.
*
* @param value the normalized value to convert
* @param toType the conversion type
* @return the converted value, which may be null
* @throws PropertyConversionException if the conversion cannot be made because of type incompatibilities
*/
<T> T convertTo(Serializable value, Class<T> toType) throws PropertyConversionException;
/**
* Validates the given value type.
* <p>
* Tests if the given value type can be converted to a normalized type and thus a value of this type can be set to
* that property.
*
* @param type the type to validate
* @return true if the type is valid, false otherwise
*/
boolean validateType(Class<?> type);
/**
* Creates a new and empty instance of a normalized value.
* <p>
* Empty is used in the sense of a value that has not been initialized or can be considered as an empty value. For
* example for the {@link String} type the empty value will be the empty string ""
*
* @return the empty instance the empty instance, or null for some implementations
*/
Object newInstance();
/**
* Method that implement the visitor pattern.
* <p>
* The visitor must return null to stop visiting children otherwise a context object that will be passed as the arg
* argument to children
*
* @param visitor the visitor to accept
* @param arg an argument passed to the visitor. This should be used by the visitor to carry on the visiting
* context.
*/
void accept(PropertyVisitor visitor, Object arg) throws PropertyException;
/**
* Compare the two properties by content.
*
* @param property
* @return true If the properties have a similar content, otherwise false
* @throws PropertyException
*/
boolean isSameAs(Property property) throws PropertyException;
/**
* Gets an iterator over the dirty children properties.
*
* @return the iterator
*/
Iterator<Property> getDirtyChildren();
/**
* @return A {@link PropertyObjectResolver} to manage this property reference to external entities, null if this
* property's type has no resolver.
* @since 7.1
*/
PropertyObjectResolver getObjectResolver();
}