/* * Copyright (C) 2005 Alfresco, Inc. * * Licensed under the Mozilla Public License version 1.1 * with a permitted attribution clause. You may obtain a * copy of the License at * * http://www.alfresco.org/legal/license.txt * * 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.alfresco.jcr.item; import java.io.InputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.List; import javax.jcr.AccessDeniedException; import javax.jcr.Item; import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.Node; import javax.jcr.Property; import javax.jcr.RepositoryException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.PropertyDefinition; import javax.jcr.version.VersionException; import org.alfresco.jcr.api.JCRNodeRef; import org.alfresco.jcr.dictionary.DataTypeMap; import org.alfresco.jcr.dictionary.PropertyDefinitionImpl; import org.alfresco.jcr.util.JCRProxyFactory; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.InvalidTypeException; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.Path; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.cmr.repository.datatype.TypeConversionException; import org.alfresco.service.namespace.QName; /** * Alfresco implementation of a Property * * @author David Caruana */ public class PropertyImpl extends ItemImpl implements Property { private NodeImpl node; private QName name; private Property proxy = null; /** * Constructor * * @param session */ public PropertyImpl(NodeImpl node, QName name) { super(node.session); this.node = node; this.name = name; } /** * Create proxied JCR Property * * @return property */ @Override public Property getProxy() { if (proxy == null) { proxy = (Property)JCRProxyFactory.create(this, Property.class, session); } return proxy; } /* (non-Javadoc) * @see javax.jcr.Item#remove() */ public void remove() throws VersionException, LockException, ConstraintViolationException, RepositoryException { setValue((Value)null); } /* (non-Javadoc) * @see javax.jcr.Property#setValue(javax.jcr.Value) */ public void setValue(Value value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { setPropertyValue(value, -1); } /* (non-Javadoc) * @see javax.jcr.Property#setValue(javax.jcr.Value[]) */ public void setValue(Value[] values) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { setPropertyValue(values, -1); } /* (non-Javadoc) * @see javax.jcr.Property#setValue(java.lang.String) */ public void setValue(String value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { setPropertyValue(value, -1); } /* (non-Javadoc) * @see javax.jcr.Property#setValue(java.lang.String[]) */ public void setValue(String[] values) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { setPropertyValue(values, -1); } /* (non-Javadoc) * @see javax.jcr.Property#setValue(java.io.InputStream) */ public void setValue(InputStream value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { setPropertyValue(value, -1); } /* (non-Javadoc) * @see javax.jcr.Property#setValue(long) */ public void setValue(long value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { setPropertyValue(value, -1); } /* (non-Javadoc) * @see javax.jcr.Property#setValue(double) */ public void setValue(double value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { setPropertyValue(value, -1); } /* (non-Javadoc) * @see javax.jcr.Property#setValue(java.util.Calendar) */ public void setValue(Calendar value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { setPropertyValue((value == null) ? null : value.getTime(), -1); } /* (non-Javadoc) * @see javax.jcr.Property#setValue(boolean) */ public void setValue(boolean value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { setPropertyValue(value, -1); } /* (non-Javadoc) * @see javax.jcr.Property#setValue(javax.jcr.Node) */ public void setValue(Node value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { setPropertyValue((value == null) ? null : JCRNodeRef.getNodeRef(value), -1); } /* (non-Javadoc) * @see javax.jcr.Property#getValue() */ public Value getValue() throws ValueFormatException, RepositoryException { checkSingleValued(); ValueImpl valueImpl = new ValueImpl(session, getType(), getPropertyValue()); // TODO: Could consider returning proxied value implementation (but i don't think is necessary) return valueImpl; } /* (non-Javadoc) * @see javax.jcr.Property#getValues() */ public Value[] getValues() throws ValueFormatException, RepositoryException { // get values from node property checkMultiValued(); Collection values = (Collection)getPropertyValue(); int type = getType(); // construct JCR wrappers List jcrValues = new ArrayList(values.size()); for (Object value : values) { // Note: In JCR all null values are stripped if (value != null) { // TODO: Could consider returning proxied value implementation (but i don't think is necessary) jcrValues.add(new ValueImpl(session, type, value)); } } return jcrValues.toArray(new Value[jcrValues.size()]); } /* (non-Javadoc) * @see javax.jcr.Property#getString() */ public String getString() throws ValueFormatException, RepositoryException { checkSingleValued(); return session.getTypeConverter().stringValue(getPropertyValue()); } /* (non-Javadoc) * @see javax.jcr.Property#getStream() */ public InputStream getStream() throws ValueFormatException, RepositoryException { checkSingleValued(); return session.getTypeConverter().streamValue(getPropertyValue()); } /* (non-Javadoc) * @see javax.jcr.Property#getLong() */ public long getLong() throws ValueFormatException, RepositoryException { checkSingleValued(); return session.getTypeConverter().longValue(getPropertyValue()); } /* (non-Javadoc) * @see javax.jcr.Property#getDouble() */ public double getDouble() throws ValueFormatException, RepositoryException { checkSingleValued(); return session.getTypeConverter().doubleValue(getPropertyValue()); } /* (non-Javadoc) * @see javax.jcr.Property#getDate() */ public Calendar getDate() throws ValueFormatException, RepositoryException { checkSingleValued(); return session.getTypeConverter().dateValue(getPropertyValue()); } /* (non-Javadoc) * @see javax.jcr.Property#getBoolean() */ public boolean getBoolean() throws ValueFormatException, RepositoryException { checkSingleValued(); return session.getTypeConverter().booleanValue(getPropertyValue()); } /* (non-Javadoc) * @see javax.jcr.Property#getNode() */ public Node getNode() throws ValueFormatException, RepositoryException { checkSingleValued(); return session.getTypeConverter().referenceValue(getPropertyValue()).getProxy(); } /* (non-Javadoc) * @see javax.jcr.Property#getLength() */ public long getLength() throws ValueFormatException, RepositoryException { checkSingleValued(); return getPropertyLength(getPropertyValue()); } /* (non-Javadoc) * @see javax.jcr.Property#getLengths() */ public long[] getLengths() throws ValueFormatException, RepositoryException { checkMultiValued(); Collection values = (Collection)getPropertyValue(); long[] lengths = new long[values.size()]; int i = 0; for (Object value : values) { lengths[i++] = getPropertyLength(value); } return lengths; } /* (non-Javadoc) * @see javax.jcr.Property#getDefinition() */ public PropertyDefinition getDefinition() throws RepositoryException { PropertyDefinitionImpl propDefImpl = new PropertyDefinitionImpl(session.getTypeManager(), getPropertyDefinition()); return propDefImpl; } /* (non-Javadoc) * @see javax.jcr.Property#getType() */ public int getType() throws RepositoryException { // TODO: The type should be based on the property value (in the case of undefined required type) return DataTypeMap.convertDataTypeToPropertyType(getPropertyDefinition().getDataType().getName()); } /* (non-Javadoc) * @see javax.jcr.Item#getName() */ public String getName() throws RepositoryException { return name.toPrefixString(session.getNamespaceResolver()); } /* (non-Javadoc) * @see javax.jcr.Item#isNode() */ public boolean isNode() { return false; } /* (non-Javadoc) * @see javax.jcr.Item#getParent() */ public Node getParent() throws ItemNotFoundException, AccessDeniedException, RepositoryException { return node.getProxy(); } /* (non-Javadoc) * @see javax.jcr.Item#getPath() */ public String getPath() throws RepositoryException { NodeService nodeService = session.getRepositoryImpl().getServiceRegistry().getNodeService(); Path path = nodeService.getPath(node.getNodeRef()); path.append(new JCRPath.SimpleElement(name)); return path.toPrefixString(session.getNamespaceResolver()); } /* (non-Javadoc) * @see javax.jcr.Item#getDepth() */ public int getDepth() throws RepositoryException { NodeService nodeService = session.getRepositoryImpl().getServiceRegistry().getNodeService(); Path path = nodeService.getPath(node.getNodeRef()); // Note: Property is one depth lower than its node return path.size(); } /* (non-Javadoc) * @see javax.jcr.Item#getAncestor(int) */ public Item getAncestor(int depth) throws ItemNotFoundException, AccessDeniedException, RepositoryException { int propertyDepth = getDepth(); if (depth < 0 || depth > propertyDepth) { throw new ItemNotFoundException("Ancestor at depth " + depth + " not found for property " + name); } if (depth == propertyDepth) { return this.getProxy(); } else { return node.getAncestor(depth -1); } } /* (non-Javadoc) * @see javax.jcr.Item#isSame(javax.jcr.Item) */ public boolean isSame(Item otherItem) throws RepositoryException { return getProxy().equals(otherItem); } /* (non-Javadoc) * @see javax.jcr.Item#accept(javax.jcr.ItemVisitor) */ public void accept(ItemVisitor visitor) throws RepositoryException { visitor.visit(getProxy()); } /** * Gets the Node Implementation that contains this property * * @return the node implementation */ protected NodeImpl getNodeImpl() { return node; } /** * Gets the Property Name * * @return the property name */ protected QName getPropertyName() { return name; } /** * Gets the property value * * @return the property value */ protected Object getPropertyValue() throws RepositoryException { Object value = null; if (getPropertyDefinition().getDataType().getName().equals(DataTypeDefinition.CONTENT)) { // Retrieve content reader as value ContentService contentService = node.session.getRepositoryImpl().getServiceRegistry().getContentService(); value = contentService.getReader(node.getNodeRef(), name); if (value == null) { // TODO: Check - If value is now null, then effectively the property has been removed throw new RepositoryException("Property " + name + " has been removed."); } } else { // TODO: We may need to copy value here... NodeService nodeService = node.session.getRepositoryImpl().getServiceRegistry().getNodeService(); value = nodeService.getProperty(node.getNodeRef(), name); if (value == null) { // TODO: Check - If value is now null, then effectively the property has been removed throw new RepositoryException("Property " + name + " has been removed."); } // Note: Internal check to ensure that value is single or multi-valued as expected boolean multiValued = getPropertyDefinition().isMultiValued(); if (multiValued != (value instanceof Collection)) { throw new RepositoryException("Alfresco value does not match multi-valued definition of " + multiValued); } } return value; } /** * Get Length of a Value * * @param value * @return * @throws ValueFormatException * @throws RepositoryException */ private long getPropertyLength(Object value) throws ValueFormatException, RepositoryException { // Handle streams if (value instanceof ContentReader) { return ((ContentReader)value).getSize(); } if (value instanceof InputStream) { return -1; } // Handle all other data types by converting to string String strValue = (String)DefaultTypeConverter.INSTANCE.convert(String.class, value); return strValue.length(); } /** * Sets a property value * * @param value the value to set * @param type type to explicitly convert to or -1 to convert to property type * @throws RepositoryException */ protected void setPropertyValue(Object value, int type) throws RepositoryException { checkSingleValued(); Object castValue = castValue(value, type); writeValue(castValue); } /** * Sets a property value * * @param values the values to set * @param type type to explicitly convert to or -1 to convert to property type * @throws RepositoryException */ protected void setPropertyValue(Object[] values, int type) throws RepositoryException { checkMultiValued(); // create collection for multi-valued property List castValues = null; if (values != null) { castValues = new ArrayList(values.length); for (Object value : values) { Object castValue = castValue(value, type); castValues.add(castValue); } } writeValue(castValues); } /** * Cast value to appropriate type for this property * * @param value value to cast * @param type -1 => cast to property type; otherwise cast to type explicitly provided * @return the cast value * @throws RepositoryException */ private Object castValue(Object value, int type) throws RepositoryException { // extract raw value if JCR value provided if (value instanceof Value) { value = ValueImpl.getValue((Value)value); } // cast value to appropriate type DataTypeDefinition dataTypeDef = getPropertyDefinition().getDataType(); if (type != -1 && dataTypeDef.getName().equals(DataTypeDefinition.ANY)) { // attempt cast to explicitly specified type, but only in case where property type can be ANY QName dataTypeName = DataTypeMap.convertPropertyTypeToDataType(type); DictionaryService dictionaryService = session.getRepositoryImpl().getServiceRegistry().getDictionaryService(); dataTypeDef = dictionaryService.getDataType(dataTypeName); if (!dataTypeName.equals(DataTypeDefinition.CONTENT)) { value = session.getTypeConverter().convert(dataTypeDef, value); } } // special case where binary is converted to inputStream ready for writing via a ContentWriter if (dataTypeDef.getName().equals(DataTypeDefinition.CONTENT)) { value = session.getTypeConverter().streamValue(value); } return value; } /** * Write the passed value to the property * * @param value value to write * @throws ValueFormatException */ private void writeValue(Object value) throws ValueFormatException { // set the property value if (value instanceof InputStream) { // write content try { ContentService contentService = session.getRepositoryImpl().getServiceRegistry().getContentService(); ContentWriter writer = contentService.getWriter(node.getNodeRef(), name, true); writer.setMimetype(MimetypeMap.MIMETYPE_BINARY); writer.putContent((InputStream)value); } catch(InvalidTypeException e) { throw new ValueFormatException(e); } } else { // write property value // Note: In the case of Content properties, this effectively "deletes" the content when the value is null try { NodeService nodeService = session.getRepositoryImpl().getServiceRegistry().getNodeService(); nodeService.setProperty(node.getNodeRef(), name, (Serializable)value); } catch(TypeConversionException e) { throw new ValueFormatException(e); } } } /** * Checks that this property is single valued. * * @throws ValueFormatException if value is multi-valued */ private void checkSingleValued() throws ValueFormatException { if (getPropertyDefinition().isMultiValued()) { // Expected exception for JSR-170 throw new ValueFormatException("Property " + name + " is multi-valued."); } } /** * Checks that this property is single valued. * * @throws ValueFormatException if value is multi-valued */ private void checkMultiValued() throws ValueFormatException { if (!getPropertyDefinition().isMultiValued()) { // Expected exception for JSR-170 throw new ValueFormatException("Property " + name + " is single-valued."); } } /** * Gets the Property Data Type * * @return the (JCR) data type */ private org.alfresco.service.cmr.dictionary.PropertyDefinition getPropertyDefinition() { DictionaryService dictionary = session.getRepositoryImpl().getServiceRegistry().getDictionaryService(); return dictionary.getProperty(name); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof PropertyImpl)) { return false; } PropertyImpl other = (PropertyImpl)obj; return this.name.equals(other.name); } @Override public int hashCode() { return name.hashCode(); } }