/** * Copyright (C) 2010 eXo Platform SAS. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.xcmis.sp.inmemory; import org.xcmis.spi.BaseItemsIterator; import org.xcmis.spi.CmisConstants; import org.xcmis.spi.CmisRuntimeException; import org.xcmis.spi.ConstraintException; import org.xcmis.spi.DocumentData; import org.xcmis.spi.FolderData; import org.xcmis.spi.ItemsIterator; import org.xcmis.spi.NameConstraintViolationException; import org.xcmis.spi.ObjectData; import org.xcmis.spi.ObjectDataVisitor; import org.xcmis.spi.ObjectNotFoundException; import org.xcmis.spi.PolicyData; import org.xcmis.spi.PropertyFilter; import org.xcmis.spi.RelationshipData; import org.xcmis.spi.StorageException; import org.xcmis.spi.TypeNotFoundException; import org.xcmis.spi.UpdateConflictException; import org.xcmis.spi.VersioningException; import org.xcmis.spi.model.AccessControlEntry; import org.xcmis.spi.model.BaseType; import org.xcmis.spi.model.ChangeEvent; import org.xcmis.spi.model.ChangeType; import org.xcmis.spi.model.Property; import org.xcmis.spi.model.PropertyDefinition; import org.xcmis.spi.model.PropertyType; import org.xcmis.spi.model.RelationshipDirection; import org.xcmis.spi.model.TypeDefinition; import org.xcmis.spi.model.Updatability; import org.xcmis.spi.model.impl.BooleanProperty; import org.xcmis.spi.model.impl.DateTimeProperty; import org.xcmis.spi.model.impl.DecimalProperty; import org.xcmis.spi.model.impl.HtmlProperty; import org.xcmis.spi.model.impl.IdProperty; import org.xcmis.spi.model.impl.IntegerProperty; import org.xcmis.spi.model.impl.StringProperty; import org.xcmis.spi.model.impl.UriProperty; import org.xcmis.spi.utils.CmisUtils; import org.xcmis.spi.utils.Logger; import java.math.BigDecimal; import java.math.BigInteger; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; /** * @author <a href="mailto:andrew00x@gmail.com">Andrey Parfonov</a> * @version $Id: BaseObjectData.java 1197 2010-05-28 08:15:37Z * alexey.zavizionov@gmail.com $ */ abstract class BaseObjectData implements ObjectData { private static final Logger LOG = Logger.getLogger(BaseObjectData.class); /** * Util method for obtaining deep copy of property. * * @param source source property * @return */ @SuppressWarnings("unchecked") static Property<?> createCopyOfProperty(Property<?> source) { switch (source.getType()) { case BOOLEAN : return new BooleanProperty(source.getId(), source.getQueryName(), source.getLocalName(), source .getDisplayName(), new ArrayList<Boolean>((List<Boolean>)source.getValues())); case DATETIME : List<Calendar> dates = (List<Calendar>)source.getValues(); List<Calendar> work = new ArrayList<Calendar>(dates.size()); for (Calendar c : dates) { work.add((Calendar)c.clone()); } return new DateTimeProperty(source.getId(), source.getQueryName(), source.getLocalName(), source .getDisplayName(), work); case DECIMAL : return new DecimalProperty(source.getId(), source.getQueryName(), source.getLocalName(), source .getDisplayName(), new ArrayList<BigDecimal>((List<BigDecimal>)source.getValues())); case HTML : return new HtmlProperty(source.getId(), source.getQueryName(), source.getLocalName(), source .getDisplayName(), new ArrayList<String>((List<String>)source.getValues())); case ID : return new IdProperty(source.getId(), source.getQueryName(), source.getLocalName(), source.getDisplayName(), new ArrayList<String>((List<String>)source.getValues())); case INTEGER : return new IntegerProperty(source.getId(), source.getQueryName(), source.getLocalName(), source .getDisplayName(), new ArrayList<BigInteger>((List<BigInteger>)source.getValues())); case STRING : return new StringProperty(source.getId(), source.getQueryName(), source.getLocalName(), source .getDisplayName(), new ArrayList<String>((List<String>)source.getValues())); case URI : return new UriProperty(source.getId(), source.getQueryName(), source.getLocalName(), source .getDisplayName(), new ArrayList<URI>((List<URI>)source.getValues())); default : return null; } } protected final TypeDefinition type; protected final StorageImpl storage; protected final Entry entry; public BaseObjectData(Entry entry, TypeDefinition type, StorageImpl storage) { this.entry = entry; this.type = type; this.storage = storage; } /** * {@inheritDoc} */ public void accept(ObjectDataVisitor visitor) { visitor.visit(this); } /** * {@inheritDoc} */ public void applyPolicy(PolicyData policy) { entry.addPolicy(policy); try { save(); } catch (StorageException e) { throw new CmisRuntimeException("Unable apply policy. " + e.getMessage(), e); } storage.changes.add(new ChangeEvent(StorageImpl.generateId(), getObjectId(), ChangeType.SECURITY, Calendar .getInstance(), null, new HashSet<String>(entry.getPolicies()), null)); } public boolean equals(Object obj) { if (obj == null) { return false; } if (obj.getClass() != getClass()) { return false; } return ((BaseObjectData)obj).getObjectId().equals(getObjectId()); } /** * {@inheritDoc} */ public List<AccessControlEntry> getACL(boolean onlyBasicPermissions) { if (!type.isControllableACL()) { return Collections.emptyList(); } return CmisUtils.createAclFromPermissionMap(entry.getPermissions()); } /** * {@inheritDoc} */ public BaseType getBaseType() { return type.getBaseId(); } /** * {@inheritDoc} */ public String getChangeToken() { return getString(CmisConstants.CHANGE_TOKEN); } /** * {@inheritDoc} */ public String getCreatedBy() { return getString(CmisConstants.CREATED_BY); } /** * {@inheritDoc} */ public Calendar getCreationDate() { return getDate(CmisConstants.CREATION_DATE); } /** * {@inheritDoc} */ public Calendar getLastModificationDate() { return getDate(CmisConstants.LAST_MODIFICATION_DATE); } /** * {@inheritDoc} */ public String getLastModifiedBy() { return getString(CmisConstants.LAST_MODIFIED_BY); } /** * {@inheritDoc} */ public String getName() { return getString(CmisConstants.NAME); } /** * {@inheritDoc} */ public String getObjectId() { return entry.getId(); } /** * {@inheritDoc} */ public FolderData getParent() throws ConstraintException { if (StorageImpl.ROOT_FOLDER_ID.equals(getObjectId())) { throw new ConstraintException("Unable get parent of root folder."); } Collection<FolderData> parents = getParents(); if (parents.size() > 1) { throw new ConstraintException("Object has more then one parent."); } if (parents.size() == 1) { return parents.iterator().next(); } return null; } /** * {@inheritDoc} */ public Collection<FolderData> getParents() { Set<String> parentIds = storage.parents.get(getObjectId()); Set<FolderData> parents = new HashSet<FolderData>(parentIds.size()); if (parentIds != null) { for (String id : parentIds) { try { parents.add((FolderData)storage.getObjectById(id)); } catch (ObjectNotFoundException e) { LOG.warn("Not found folder " + id); } } } return parents; } /** * {@inheritDoc} */ public Collection<PolicyData> getPolicies() { if (!type.isControllablePolicy()) { return Collections.emptyList(); } List<PolicyData> policies = new ArrayList<PolicyData>(); for (String id : entry.getPolicies()) { try { policies.add((PolicyData)storage.getObjectById(id)); } catch (ObjectNotFoundException e) { LOG.warn("Not found policy " + id); } } return policies; } /** * {@inheritDoc} */ public Map<String, Property<?>> getProperties() { Map<String, Property<?>> properties = new HashMap<String, Property<?>>(); for (PropertyDefinition<?> definition : type.getPropertyDefinitions()) { properties.put(definition.getId(), doGetProperty(definition)); } return properties; } /** * {@inheritDoc} */ public Map<String, Property<?>> getProperties(PropertyFilter filter) { Map<String, Property<?>> properties = new HashMap<String, Property<?>>(); for (PropertyDefinition<?> definition : type.getPropertyDefinitions()) { String queryName = definition.getQueryName(); if (filter.accept(queryName)) { String id = definition.getId(); properties.put(id, doGetProperty(definition)); } } return properties; } /** * {@inheritDoc} */ public Property<?> getProperty(String id) { PropertyDefinition<?> definition = type.getPropertyDefinition(id); if (definition != null) { return doGetProperty(definition); } return null; } /** * {@inheritDoc} */ public ItemsIterator<RelationshipData> getRelationships(RelationshipDirection direction, TypeDefinition type, boolean includeSubRelationshipTypes) { Set<String> relationshipIds = storage.relationships.get(getObjectId()); if (relationshipIds == null) { return CmisUtils.emptyItemsIterator(); } Collection<String> typeFilter = new HashSet<String>(); typeFilter.add(type.getId()); if (includeSubRelationshipTypes) { Collection<TypeDefinition> subTypes = null; try { subTypes = storage.getSubTypes(type.getId(), false); } catch (TypeNotFoundException e) { // Should never happen. throw new CmisRuntimeException(e.getMessage(), e); } for (TypeDefinition t : subTypes) { typeFilter.add(t.getId()); } } Set<RelationshipData> relationships = new java.util.HashSet<RelationshipData>(); for (String id : relationshipIds) { RelationshipData relationship = null; try { relationship = (RelationshipData)storage.getObjectById(id); } catch (ObjectNotFoundException e) { LOG.warn("Not found relationship " + id + "."); continue; } if ((direction == RelationshipDirection.EITHER // || (direction == RelationshipDirection.SOURCE && relationship.getSourceId().equals(getObjectId())) // || (direction == RelationshipDirection.TARGET && relationship.getTargetId().equals(getObjectId()))) && typeFilter.contains(relationship.getTypeDefinition().getId())) { relationships.add(relationship); } } return new BaseItemsIterator<RelationshipData>(relationships); } /** * {@inheritDoc} */ public TypeDefinition getTypeDefinition() { return type; } /** * {@inheritDoc} */ public String getTypeId() { return type.getId(); } public int hashCode() { int hash = 8; return hash * 31 + getObjectId().hashCode(); } /** * {@inheritDoc} */ public void removePolicy(PolicyData policy) { entry.removePolicy(policy); try { save(); } catch (StorageException e) { throw new CmisRuntimeException("Unable remove policy. " + e.getMessage(), e); } storage.changes.add(new ChangeEvent(StorageImpl.generateId(), getObjectId(), ChangeType.SECURITY, Calendar .getInstance(), null, new HashSet<String>(entry.getPolicies()), null)); } /** * {@inheritDoc} */ public void setACL(List<AccessControlEntry> acl) { Map<String, Set<String>> permissions = entry.getPermissions(); permissions.clear(); CmisUtils.addAclToPermissionMap(permissions, acl); try { save(); } catch (StorageException e) { throw new CmisRuntimeException("Unable set ACL. " + e.getMessage(), e); } if (acl != null) { // null may mind replace all applied ACL List<AccessControlEntry> copy = new ArrayList<AccessControlEntry>(acl.size()); for (AccessControlEntry ace : acl) { copy.add(new AccessControlEntry(ace.getPrincipal(), new HashSet<String>(ace.getPermissions()), ace .isDirect())); } storage.changes.add(new ChangeEvent(StorageImpl.generateId(), getObjectId(), ChangeType.SECURITY, Calendar .getInstance(), null, null, copy)); } } /** * {@inheritDoc} */ public void setProperties(Map<String, Property<?>> properties) throws NameConstraintViolationException, UpdateConflictException, VersioningException, StorageException { List<Property<?>> chl = new ArrayList<Property<?>>(); for (Property<?> property : properties.values()) { if (doSetProperty(property)) { chl.add(createCopyOfProperty(property)); } } save(); storage.changes.add(new ChangeEvent(StorageImpl.generateId(), getObjectId(), ChangeType.UPDATED, Calendar .getInstance(), chl, null, null)); } /** * {@inheritDoc} */ public void setProperty(Property<?> property) throws NameConstraintViolationException, StorageException, UpdateConflictException, VersioningException { doSetProperty(property); save(); List<Property<?>> chl = new ArrayList<Property<?>>(1); // Create copy of property to be sure it will unchangeable in changes log. chl.add(createCopyOfProperty(property)); storage.changes.add(new ChangeEvent(StorageImpl.generateId(), getObjectId(), ChangeType.UPDATED, Calendar .getInstance(), chl, null, null)); } /** * {@inheritDoc} */ public String toString() { return "type: " + getTypeId() + ", name: " + getName() + ", id: " + getObjectId(); } /** * To create the new property. * * @param def the property definition * @param value the value * @return the new property */ private Property<?> createProperty(PropertyDefinition<?> def, Value value) { if (def.getPropertyType() == PropertyType.BOOLEAN) { return new BooleanProperty(def.getId(), def.getQueryName(), def.getLocalName(), def.getDisplayName(), value == null ? null : Arrays.asList(value.getBooleans())); } else if (def.getPropertyType() == PropertyType.DATETIME) { return new DateTimeProperty(def.getId(), def.getQueryName(), def.getLocalName(), def.getDisplayName(), value == null ? null : Arrays.asList(value.getDates())); } else if (def.getPropertyType() == PropertyType.DECIMAL) { return new DecimalProperty(def.getId(), def.getQueryName(), def.getLocalName(), def.getDisplayName(), value == null ? null : Arrays.asList(value.getDecimals())); } else if (def.getPropertyType() == PropertyType.HTML) { return new HtmlProperty(def.getId(), def.getQueryName(), def.getLocalName(), def.getDisplayName(), value == null ? null : Arrays.asList(value.getStrings())); } else if (def.getPropertyType() == PropertyType.ID) { return new IdProperty(def.getId(), def.getQueryName(), def.getLocalName(), def.getDisplayName(), value == null ? null : Arrays.asList(value.getStrings())); } else if (def.getPropertyType() == PropertyType.INTEGER) { return new IntegerProperty(def.getId(), def.getQueryName(), def.getLocalName(), def.getDisplayName(), value == null ? null : Arrays.asList(value.getIntegers())); } else if (def.getPropertyType() == PropertyType.STRING) { return new StringProperty(def.getId(), def.getQueryName(), def.getLocalName(), def.getDisplayName(), value == null ? null : Arrays.asList(value.getStrings())); } else if (def.getPropertyType() == PropertyType.URI) { return new UriProperty(def.getId(), def.getQueryName(), def.getLocalName(), def.getDisplayName(), value == null ? null : Arrays.asList(value.getURI())); } else { throw new CmisRuntimeException("Unknown property type."); } } protected abstract void delete() throws StorageException, UpdateConflictException, VersioningException; protected Property<?> doGetProperty(PropertyDefinition<?> definition) { Value value = entry.getValue(definition.getId()); if (value == null && CmisConstants.PATH.equals(definition.getId()) && type.getBaseId() == BaseType.FOLDER) { value = new StringValue(((FolderData)this).getPath()); // add other virtual property } return createProperty(definition, value); } /** * Update properties, skip on-create and read-only properties * * @param property property to be updated * @return <code>true</code> if property was updated and <code>false</code> * otherwise, e.g. property was not updated since it is read-only * property */ protected boolean doSetProperty(Property<?> property) throws NameConstraintViolationException { PropertyDefinition<?> definition = type.getPropertyDefinition(property.getId()); Updatability updatability = definition.getUpdatability(); if (updatability == Updatability.READWRITE // || (updatability == Updatability.WHENCHECKEDOUT && getBaseType() == BaseType.DOCUMENT && ((DocumentData)this) .isPWC())) { // Do not store nulls for (Iterator<?> i = property.getValues().iterator(); i.hasNext();) { Object v = i.next(); if (v == null) { i.remove(); } } if (CmisConstants.NAME.equals(property.getId())) { String name = null; List<?> values = property.getValues(); if (values.size() > 0) { name = (String)values.get(0); } if (name == null || name.length() == 0) { throw new NameConstraintViolationException("Name can't be null or empty string."); } if (name.equals(getName())) { return false; } for (FolderData parent : getParents()) { for (ItemsIterator<ObjectData> iterator = parent.getChildren(null); iterator.hasNext();) { if (name.equals(iterator.next().getName())) { throw new NameConstraintViolationException("Object with name " + name + " already exists in parent folder."); } } } } entry.setProperty(property); return true; } else { if (LOG.isDebugEnabled()) { LOG.debug("Property " + property.getId() + " is not updatable."); } return false; } } protected Boolean getBoolean(String id) { Value value = entry.getValue(id); if (value != null) { Boolean[] booleans = value.getBooleans(); return booleans.length > 0 ? booleans[0] : null; } return null; } protected Boolean[] getBooleans(String id) { Value value = entry.getValue(id); if (value != null) { return value.getBooleans(); } return null; } protected Calendar getDate(String id) { Value value = entry.getValue(id); if (value != null) { Calendar[] dates = value.getDates(); return dates.length > 0 ? dates[0] : null; } return null; } protected Calendar[] getDates(String id) { Value value = entry.getValue(id); if (value != null) { return value.getDates(); } return null; } protected BigDecimal getDecimal(String id) { Value value = entry.getValue(id); if (value != null) { BigDecimal[] decimals = value.getDecimals(); return decimals.length > 0 ? decimals[0] : null; } return null; } protected BigDecimal[] getDecimals(String id) { Value value = entry.getValue(id); if (value != null) { return value.getDecimals(); } return null; } protected Entry getEntry() { return entry; } protected String getId(String id) { return getString(id); } protected String[] getIds(String id) { return getStrings(id); } protected BigInteger getInteger(String id) { Value value = entry.getValue(id); if (value != null) { BigInteger[] integers = value.getIntegers(); return integers.length > 0 ? integers[0] : null; } return null; } protected BigInteger[] getIntegers(String id) { Value value = entry.getValue(id); if (value != null) { return value.getIntegers(); } return null; } protected String getString(String id) { Value value = entry.getValue(id); if (value != null) { String[] strings = value.getStrings(); return strings.length > 0 ? strings[0] : null; } return null; } protected String[] getStrings(String id) { Value value = entry.getValue(id); if (value != null) { return value.getStrings(); } return null; } protected void save() throws StorageException { if (storage.entries.get(entry.getId()) == null) { throw new CmisRuntimeException("Object was removed from storage."); } entry.setValue(CmisConstants.LAST_MODIFIED_BY, new StringValue(storage.getCurrentUser())); entry.setValue(CmisConstants.LAST_MODIFICATION_DATE, new DateValue(Calendar.getInstance())); entry.setValue(CmisConstants.CHANGE_TOKEN, new StringValue(StorageImpl.generateId())); storage.entries.put(entry.getId(), entry); storage.indexListener.updated(this); } }