/* * (C) Copyright 2014 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: * Florent Guillaume */ package org.nuxeo.ecm.core.storage.dbs; import static java.lang.Boolean.TRUE; import java.io.Serializable; 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.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Consumer; import org.apache.commons.lang.StringUtils; import org.nuxeo.ecm.core.NXCore; import org.nuxeo.ecm.core.api.DocumentNotFoundException; import org.nuxeo.ecm.core.api.LifeCycleException; import org.nuxeo.ecm.core.api.Lock; import org.nuxeo.ecm.core.api.NuxeoException; import org.nuxeo.ecm.core.api.PropertyException; import org.nuxeo.ecm.core.api.model.DocumentPart; import org.nuxeo.ecm.core.api.model.Property; import org.nuxeo.ecm.core.api.model.PropertyNotFoundException; import org.nuxeo.ecm.core.api.model.ReadOnlyPropertyException; import org.nuxeo.ecm.core.api.model.impl.ComplexProperty; import org.nuxeo.ecm.core.blob.DocumentBlobManager; import org.nuxeo.ecm.core.lifecycle.LifeCycle; import org.nuxeo.ecm.core.lifecycle.LifeCycleService; import org.nuxeo.ecm.core.model.Document; import org.nuxeo.ecm.core.model.LockManager; import org.nuxeo.ecm.core.model.Session; import org.nuxeo.ecm.core.schema.DocumentType; import org.nuxeo.ecm.core.schema.SchemaManager; import org.nuxeo.ecm.core.schema.types.ComplexType; import org.nuxeo.ecm.core.schema.types.CompositeType; import org.nuxeo.ecm.core.schema.types.Field; import org.nuxeo.ecm.core.schema.types.Schema; import org.nuxeo.ecm.core.schema.types.Type; import org.nuxeo.ecm.core.storage.BaseDocument; import org.nuxeo.ecm.core.storage.State; import org.nuxeo.ecm.core.storage.sql.coremodel.SQLDocumentVersion.VersionNotModifiableException; import org.nuxeo.runtime.api.Framework; /** * Implementation of a {@link Document} for Document-Based Storage. The document is stored as a JSON-like Map. The keys * of the Map are the property names (including special names for system properties), and the values Map are * Serializable values, either: * <ul> * <li>a scalar (String, Long, Double, Boolean, Calendar, Binary), * <li>an array of scalars, * <li>a List of Maps, recursively, * <li>or another Map, recursively. * </ul> * An ACP value is stored as a list of maps. Each map has a keys for the ACL name and the actual ACL which is a list of * ACEs. An ACE is a map having as keys username, permission, and grant. * * @since 5.9.4 */ public class DBSDocument extends BaseDocument<State> { private static final Long ZERO = Long.valueOf(0); public static final String SYSPROP_FULLTEXT_SIMPLE = "fulltextSimple"; public static final String SYSPROP_FULLTEXT_BINARY = "fulltextBinary"; public static final String SYSPROP_FULLTEXT_JOBID = "fulltextJobId"; public static final String KEY_PREFIX = "ecm:"; public static final String KEY_ID = "ecm:id"; public static final String KEY_PARENT_ID = "ecm:parentId"; public static final String KEY_ANCESTOR_IDS = "ecm:ancestorIds"; public static final String KEY_PRIMARY_TYPE = "ecm:primaryType"; public static final String KEY_MIXIN_TYPES = "ecm:mixinTypes"; public static final String KEY_NAME = "ecm:name"; public static final String KEY_POS = "ecm:pos"; public static final String KEY_ACP = "ecm:acp"; public static final String KEY_ACL_NAME = "name"; public static final String KEY_PATH_INTERNAL = "ecm:__path"; public static final String KEY_ACL = "acl"; public static final String KEY_ACE_USER = "user"; public static final String KEY_ACE_PERMISSION = "perm"; public static final String KEY_ACE_GRANT = "grant"; public static final String KEY_ACE_CREATOR = "creator"; public static final String KEY_ACE_BEGIN = "begin"; public static final String KEY_ACE_END = "end"; public static final String KEY_ACE_STATUS = "status"; public static final String KEY_READ_ACL = "ecm:racl"; public static final String KEY_IS_CHECKED_IN = "ecm:isCheckedIn"; public static final String KEY_IS_VERSION = "ecm:isVersion"; public static final String KEY_IS_LATEST_VERSION = "ecm:isLatestVersion"; public static final String KEY_IS_LATEST_MAJOR_VERSION = "ecm:isLatestMajorVersion"; public static final String KEY_MAJOR_VERSION = "ecm:majorVersion"; public static final String KEY_MINOR_VERSION = "ecm:minorVersion"; public static final String KEY_VERSION_SERIES_ID = "ecm:versionSeriesId"; public static final String KEY_VERSION_CREATED = "ecm:versionCreated"; public static final String KEY_VERSION_LABEL = "ecm:versionLabel"; public static final String KEY_VERSION_DESCRIPTION = "ecm:versionDescription"; public static final String KEY_BASE_VERSION_ID = "ecm:baseVersionId"; public static final String KEY_IS_PROXY = "ecm:isProxy"; public static final String KEY_PROXY_TARGET_ID = "ecm:proxyTargetId"; public static final String KEY_PROXY_VERSION_SERIES_ID = "ecm:proxyVersionSeriesId"; public static final String KEY_PROXY_IDS = "ecm:proxyIds"; public static final String KEY_LIFECYCLE_POLICY = "ecm:lifeCyclePolicy"; public static final String KEY_LIFECYCLE_STATE = "ecm:lifeCycleState"; public static final String KEY_LOCK_OWNER = "ecm:lockOwner"; public static final String KEY_LOCK_CREATED = "ecm:lockCreated"; public static final String KEY_CHANGE_TOKEN = "ecm:changeToken"; // used instead of ecm:changeToken when change tokens are disabled public static final String KEY_DC_MODIFIED = "dc:modified"; public static final String KEY_BLOB_NAME = "name"; public static final String KEY_BLOB_MIME_TYPE = "mime-type"; public static final String KEY_BLOB_ENCODING = "encoding"; public static final String KEY_BLOB_DIGEST = "digest"; public static final String KEY_BLOB_LENGTH = "length"; public static final String KEY_BLOB_DATA = "data"; public static final String KEY_FULLTEXT_SIMPLE = "ecm:fulltextSimple"; public static final String KEY_FULLTEXT_BINARY = "ecm:fulltextBinary"; public static final String KEY_FULLTEXT_JOBID = "ecm:fulltextJobId"; public static final String KEY_FULLTEXT_SCORE = "ecm:fulltextScore"; public static final String APPLICATION_OCTET_STREAM = "application/octet-stream"; public static final String INITIAL_CHANGE_TOKEN = "0"; protected final String id; protected final DBSDocumentState docState; protected final DocumentType type; protected final List<Schema> proxySchemas; protected final DBSSession session; protected boolean readonly; protected static final Map<String, String> systemPropNameMap; static { systemPropNameMap = new HashMap<String, String>(); systemPropNameMap.put(SYSPROP_FULLTEXT_JOBID, KEY_FULLTEXT_JOBID); } public DBSDocument(DBSDocumentState docState, DocumentType type, DBSSession session, boolean readonly) { // no state for NullDocument (parent of placeless children) this.id = docState == null ? null : (String) docState.get(KEY_ID); this.docState = docState; this.type = type; this.session = session; if (docState != null && isProxy()) { SchemaManager schemaManager = Framework.getService(SchemaManager.class); proxySchemas = schemaManager.getProxySchemas(type.getName()); } else { proxySchemas = null; } this.readonly = readonly; } @Override public DocumentType getType() { return type; } @Override public Session getSession() { return session; } @Override public String getRepositoryName() { return session.getRepositoryName(); } @Override protected List<Schema> getProxySchemas() { return proxySchemas; } @Override public String getUUID() { return id; } @Override public String getName() { return docState.getName(); } @Override public Long getPos() { return (Long) docState.get(KEY_POS); } @Override public Document getParent() { if (isVersion()) { Document workingCopy = session.getDocument(getVersionSeriesId()); return workingCopy == null ? null : workingCopy.getParent(); } String parentId = docState.getParentId(); return parentId == null ? null : session.getDocument(parentId); } @Override public boolean isProxy() { return TRUE.equals(docState.get(KEY_IS_PROXY)); } @Override public boolean isVersion() { return TRUE.equals(docState.get(KEY_IS_VERSION)); } @Override public String getPath() { if (isVersion()) { Document workingCopy = session.getDocument(getVersionSeriesId()); return workingCopy == null ? null : workingCopy.getPath(); } String name = getName(); Document doc = getParent(); if (doc == null) { if ("".equals(name)) { return "/"; // root } else { return name; // placeless, no slash } } LinkedList<String> list = new LinkedList<String>(); list.addFirst(name); while (doc != null) { list.addFirst(doc.getName()); doc = doc.getParent(); } return StringUtils.join(list, '/'); } @Override public Document getChild(String name) { return session.getChild(id, name); } @Override public List<Document> getChildren() { if (!isFolder()) { return Collections.emptyList(); } return session.getChildren(id); } @Override public List<String> getChildrenIds() { if (!isFolder()) { return Collections.emptyList(); } return session.getChildrenIds(id); } @Override public boolean hasChild(String name) { if (!isFolder()) { return false; } return session.hasChild(id, name); } @Override public boolean hasChildren() { if (!isFolder()) { return false; } return session.hasChildren(id); } @Override public Document addChild(String name, String typeName) { if (!isFolder()) { throw new IllegalArgumentException("Not a folder"); } return session.createChild(null, id, name, null, typeName); } @Override public void orderBefore(String src, String dest) { Document srcDoc = getChild(src); if (srcDoc == null) { throw new DocumentNotFoundException("Document " + this + " has no child: " + src); } Document destDoc; if (dest == null) { destDoc = null; } else { destDoc = getChild(dest); if (destDoc == null) { throw new DocumentNotFoundException("Document " + this + " has no child: " + dest); } } session.orderBefore(id, srcDoc.getUUID(), destDoc == null ? null : destDoc.getUUID()); } // simple property only @Override public Serializable getPropertyValue(String name) { DBSDocumentState docState = getStateOrTarget(name); return docState.get(name); } // simple property only @Override public void setPropertyValue(String name, Serializable value) { DBSDocumentState docState = getStateOrTarget(name); docState.put(name, value); } // helpers for getValue / setValue @Override protected State getChild(State state, String name, Type type) { return (State) state.get(name); } @Override protected State getChildForWrite(State state, String name, Type type) throws PropertyException { State child = getChild(state, name, type); if (child == null) { state.put(name, child = new State()); } return child; } @Override protected List<State> getChildAsList(State state, String name) { @SuppressWarnings("unchecked") List<State> list = (List<State>) state.get(name); if (list == null) { list = new ArrayList<>(); } return list; } @Override protected void updateList(State state, String name, Field field, String xpath, List<Object> values) { List<State> childStates = new ArrayList<>(values.size()); int i = 0; for (Object v : values) { State childState = new State(); setValueComplex(childState, field, xpath + '/' + i, v); childStates.add(childState); i++; } state.put(name, (Serializable) childStates); } @Override protected List<State> updateList(State state, String name, Property property) throws PropertyException { Collection<Property> properties = property.getChildren(); int newSize = properties.size(); @SuppressWarnings("unchecked") List<State> childStates = (List<State>) state.get(name); if (childStates == null) { childStates = new ArrayList<>(newSize); state.put(name, (Serializable) childStates); } int oldSize = childStates.size(); // remove extra list elements if (oldSize > newSize) { for (int i = oldSize - 1; i >= newSize; i--) { childStates.remove(i); } } // add new list elements if (oldSize < newSize) { for (int i = oldSize; i < newSize; i++) { childStates.add(new State()); } } return childStates; } @Override public Object getValue(String xpath) throws PropertyException { DBSDocumentState docState = getStateOrTarget(xpath); return getValueObject(docState.getState(), xpath); } @Override public void setValue(String xpath, Object value) throws PropertyException { DBSDocumentState docState = getStateOrTarget(xpath); // markDirty has to be called *before* we change the state docState.markDirty(); setValueObject(docState.getState(), xpath, value); } @Override public void visitBlobs(Consumer<BlobAccessor> blobVisitor) throws PropertyException { if (isProxy()) { getTargetDocument().visitBlobs(blobVisitor); // fall through for proxy schemas } Runnable markDirty = () -> docState.markDirty(); visitBlobs(docState.getState(), blobVisitor, markDirty); } @Override public Document checkIn(String label, String checkinComment) { if (isProxy()) { return getTargetDocument().checkIn(label, checkinComment); } else if (isVersion()) { throw new VersionNotModifiableException(); } else { Document version = session.checkIn(id, label, checkinComment); DocumentBlobManager blobManager = Framework.getService(DocumentBlobManager.class); blobManager.freezeVersion(version); return version; } } @Override public void checkOut() { if (isProxy()) { getTargetDocument().checkOut(); } else if (isVersion()) { throw new VersionNotModifiableException(); } else { session.checkOut(id); } } @Override public List<String> getVersionsIds() { return session.getVersionsIds(getVersionSeriesId()); } @Override public List<Document> getVersions() { List<String> ids = session.getVersionsIds(getVersionSeriesId()); List<Document> versions = new ArrayList<Document>(); for (String id : ids) { versions.add(session.getDocument(id)); } return versions; } @Override public Document getLastVersion() { return session.getLastVersion(getVersionSeriesId()); } @Override public Document getSourceDocument() { if (isProxy()) { return getTargetDocument(); } else if (isVersion()) { return getWorkingCopy(); } else { return this; } } @Override public void restore(Document version) { if (!version.isVersion()) { throw new NuxeoException("Cannot restore a non-version: " + version); } session.restoreVersion(this, version); } @Override public Document getVersion(String label) { DBSDocumentState state = session.getVersionByLabel(getVersionSeriesId(), label); return session.getDocument(state); } @Override public Document getBaseVersion() { if (isProxy()) { return getTargetDocument().getBaseVersion(); } else if (isVersion()) { return null; } else { if (isCheckedOut()) { return null; } else { String id = (String) docState.get(KEY_BASE_VERSION_ID); if (id == null) { // shouldn't happen return null; } return session.getDocument(id); } } } @Override public boolean isCheckedOut() { if (isProxy()) { return getTargetDocument().isCheckedOut(); } else if (isVersion()) { return false; } else { return !TRUE.equals(docState.get(KEY_IS_CHECKED_IN)); } } @Override public String getVersionSeriesId() { if (isProxy()) { return (String) docState.get(KEY_PROXY_VERSION_SERIES_ID); } else if (isVersion()) { return (String) docState.get(KEY_VERSION_SERIES_ID); } else { return getUUID(); } } @Override public Calendar getVersionCreationDate() { DBSDocumentState docState = getStateOrTarget(); return (Calendar) docState.get(KEY_VERSION_CREATED); } @Override public String getVersionLabel() { DBSDocumentState docState = getStateOrTarget(); return (String) docState.get(KEY_VERSION_LABEL); } @Override public String getCheckinComment() { DBSDocumentState docState = getStateOrTarget(); return (String) docState.get(KEY_VERSION_DESCRIPTION); } @Override public boolean isLatestVersion() { return isEqualOnVersion(TRUE, KEY_IS_LATEST_VERSION); } @Override public boolean isMajorVersion() { return isEqualOnVersion(ZERO, KEY_MINOR_VERSION); } @Override public boolean isLatestMajorVersion() { return isEqualOnVersion(TRUE, KEY_IS_LATEST_MAJOR_VERSION); } protected boolean isEqualOnVersion(Object ob, String key) { if (isProxy()) { // TODO avoid getting the target just to check if it's a version // use another specific property instead if (getTargetDocument().isVersion()) { return ob.equals(docState.get(key)); } else { // if live version, return false return false; } } else if (isVersion()) { return ob.equals(docState.get(key)); } else { return false; } } @Override public boolean isVersionSeriesCheckedOut() { if (isProxy() || isVersion()) { Document workingCopy = getWorkingCopy(); return workingCopy == null ? false : workingCopy.isCheckedOut(); } else { return isCheckedOut(); } } @Override public Document getWorkingCopy() { if (isProxy() || isVersion()) { String versionSeriesId = getVersionSeriesId(); return versionSeriesId == null ? null : session.getDocument(versionSeriesId); } else { return this; } } @Override public boolean isFolder() { return type == null // null document || type.isFolder(); } @Override public void setReadOnly(boolean readonly) { this.readonly = readonly; } @Override public boolean isReadOnly() { return readonly; } @Override public void remove() { session.remove(id); } @Override public String getLifeCycleState() { DBSDocumentState docState = getStateOrTarget(); return (String) docState.get(KEY_LIFECYCLE_STATE); } @Override public void setCurrentLifeCycleState(String lifeCycleState) throws LifeCycleException { DBSDocumentState docState = getStateOrTarget(); docState.put(KEY_LIFECYCLE_STATE, lifeCycleState); DocumentBlobManager blobManager = Framework.getService(DocumentBlobManager.class); blobManager.notifyChanges(this, Collections.singleton(KEY_LIFECYCLE_STATE)); } @Override public String getLifeCyclePolicy() { DBSDocumentState docState = getStateOrTarget(); return (String) docState.get(KEY_LIFECYCLE_POLICY); } @Override public void setLifeCyclePolicy(String policy) throws LifeCycleException { DBSDocumentState docState = getStateOrTarget(); docState.put(KEY_LIFECYCLE_POLICY, policy); DocumentBlobManager blobManager = Framework.getService(DocumentBlobManager.class); blobManager.notifyChanges(this, Collections.singleton(KEY_LIFECYCLE_POLICY)); } // TODO generic @Override public void followTransition(String transition) throws LifeCycleException { LifeCycleService service = NXCore.getLifeCycleService(); if (service == null) { throw new LifeCycleException("LifeCycleService not available"); } service.followTransition(this, transition); } // TODO generic @Override public Collection<String> getAllowedStateTransitions() throws LifeCycleException { LifeCycleService service = NXCore.getLifeCycleService(); if (service == null) { throw new LifeCycleException("LifeCycleService not available"); } LifeCycle lifeCycle = service.getLifeCycleFor(this); if (lifeCycle == null) { return Collections.emptyList(); } return lifeCycle.getAllowedStateTransitionsFrom(getLifeCycleState()); } @Override public void setSystemProp(String name, Serializable value) { String propertyName; if (name.startsWith(SYSPROP_FULLTEXT_SIMPLE)) { propertyName = name.replace(SYSPROP_FULLTEXT_SIMPLE, KEY_FULLTEXT_SIMPLE); } else if (name.startsWith(SYSPROP_FULLTEXT_BINARY)) { propertyName = name.replace(SYSPROP_FULLTEXT_BINARY, KEY_FULLTEXT_BINARY); } else { propertyName = systemPropNameMap.get(name); } if (propertyName == null) { throw new PropertyNotFoundException(name, "Unknown system property"); } setPropertyValue(propertyName, value); } @SuppressWarnings("unchecked") @Override public <T extends Serializable> T getSystemProp(String name, Class<T> type) { String propertyName = systemPropNameMap.get(name); if (propertyName == null) { throw new PropertyNotFoundException(name, "Unknown system property: "); } Serializable value = getPropertyValue(propertyName); if (value == null) { if (type == Boolean.class) { value = Boolean.FALSE; } else if (type == Long.class) { value = Long.valueOf(0); } } return (T) value; } @Override public String getChangeToken() { DBSDocumentState docState = getStateOrTarget(); if (session.changeTokenEnabled) { return (String) docState.get(KEY_CHANGE_TOKEN); } else { Calendar modified = (Calendar) docState.get(KEY_DC_MODIFIED); return modified == null ? null : String.valueOf(modified.getTimeInMillis()); } } protected DBSDocumentState getStateOrTarget(Type type) throws PropertyException { return getStateOrTargetForSchema(type.getName()); } protected DBSDocumentState getStateOrTarget(String xpath) { return getStateOrTargetForSchema(getSchema(xpath)); } /** * Checks if the given schema should be resolved on the proxy or the target. */ protected DBSDocumentState getStateOrTargetForSchema(String schema) { if (isProxy() && !isSchemaForProxy(schema)) { return getTargetDocument().docState; } else { return docState; } } /** * Gets the target state if this is a proxy, or the regular state otherwise. */ protected DBSDocumentState getStateOrTarget() { if (isProxy()) { return getTargetDocument().docState; } else { return docState; } } protected boolean isSchemaForProxy(String schema) { SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class); return schemaManager.isProxySchema(schema, getType().getName()); } protected String getSchema(String xpath) { switch (xpath) { case KEY_MAJOR_VERSION: case KEY_MINOR_VERSION: case "major_version": case "minor_version": return "uid"; case KEY_FULLTEXT_JOBID: case KEY_LIFECYCLE_POLICY: case KEY_LIFECYCLE_STATE: return "__ecm__"; } if (xpath.startsWith(KEY_FULLTEXT_SIMPLE) || xpath.startsWith(KEY_FULLTEXT_BINARY)) { return "__ecm__"; } String[] segments = xpath.split("/"); String segment = segments[0]; Field field = type.getField(segment); if (field == null) { // check facets SchemaManager schemaManager = Framework.getService(SchemaManager.class); for (String facet : getFacets()) { CompositeType facetType = schemaManager.getFacet(facet); field = facetType.getField(segment); if (field != null) { break; } } } if (field == null && getProxySchemas() != null) { // check proxy schemas for (Schema schema : getProxySchemas()) { field = schema.getField(segment); if (field != null) { break; } } } if (field == null) { throw new PropertyNotFoundException(xpath); } return field.getDeclaringType().getName(); } @Override public void readDocumentPart(DocumentPart dp) throws PropertyException { DBSDocumentState docState = getStateOrTarget(dp.getType()); readComplexProperty(docState.getState(), (ComplexProperty) dp); } @Override protected String internalName(String name) { switch (name) { case "major_version": return KEY_MAJOR_VERSION; case "minor_version": return KEY_MINOR_VERSION; } return name; } @Override public Map<String, Serializable> readPrefetch(ComplexType complexType, Set<String> xpaths) throws PropertyException { DBSDocumentState docState = getStateOrTarget(complexType); return readPrefetch(docState.getState(), complexType, xpaths); } @Override public boolean writeDocumentPart(DocumentPart dp, WriteContext writeContext) throws PropertyException { DBSDocumentState docState = getStateOrTarget(dp.getType()); // markDirty has to be called *before* we change the state docState.markDirty(); boolean changed = writeComplexProperty(docState.getState(), (ComplexProperty) dp, writeContext); clearDirtyFlags(dp); return changed; } @Override public Set<String> getAllFacets() { Set<String> facets = new HashSet<String>(getType().getFacets()); facets.addAll(Arrays.asList(getFacets())); return facets; } @Override public String[] getFacets() { DBSDocumentState docState = getStateOrTarget(); Object[] mixins = (Object[]) docState.get(KEY_MIXIN_TYPES); if (mixins == null) { return EMPTY_STRING_ARRAY; } else { String[] res = new String[mixins.length]; System.arraycopy(mixins, 0, res, 0, mixins.length); return res; } } @Override public boolean hasFacet(String facet) { return getAllFacets().contains(facet); } @Override public boolean addFacet(String facet) { if (getType().getFacets().contains(facet)) { return false; // already present in type } DBSDocumentState docState = getStateOrTarget(); Object[] mixins = (Object[]) docState.get(KEY_MIXIN_TYPES); if (mixins == null) { mixins = new Object[] { facet }; } else { List<Object> list = Arrays.asList(mixins); if (list.contains(facet)) { return false; // already present in doc } list = new ArrayList<Object>(list); list.add(facet); mixins = list.toArray(new Object[list.size()]); } docState.put(KEY_MIXIN_TYPES, mixins); return true; } @Override public boolean removeFacet(String facet) { DBSDocumentState docState = getStateOrTarget(); Object[] mixins = (Object[]) docState.get(KEY_MIXIN_TYPES); if (mixins == null) { return false; } List<Object> list = new ArrayList<Object>(Arrays.asList(mixins)); if (!list.remove(facet)) { return false; // not present in doc } mixins = list.toArray(new Object[list.size()]); if (mixins.length == 0) { mixins = null; } docState.put(KEY_MIXIN_TYPES, mixins); // remove the fields belonging to the facet // except for schemas still present due to the primary type or another facet SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class); CompositeType ft = schemaManager.getFacet(facet); Set<String> otherSchemas = getSchemas(getType(), list); for (Schema schema : ft.getSchemas()) { if (otherSchemas.contains(schema.getName())) { continue; } for (Field field : schema.getFields()) { String name = field.getName().getPrefixedName(); if (docState.containsKey(name)) { docState.put(name, null); } } } return true; } protected static Set<String> getSchemas(DocumentType type, List<Object> facets) { SchemaManager schemaManager = Framework.getService(SchemaManager.class); Set<String> schemas = new HashSet<>(Arrays.asList(type.getSchemaNames())); for (Object facet : facets) { CompositeType ft = schemaManager.getFacet((String) facet); if (ft != null) { schemas.addAll(Arrays.asList(ft.getSchemaNames())); } } return schemas; } @Override public DBSDocument getTargetDocument() { if (isProxy()) { String targetId = (String) docState.get(KEY_PROXY_TARGET_ID); return session.getDocument(targetId); } else { return null; } } @Override public void setTargetDocument(Document target) { if (isProxy()) { if (isReadOnly()) { throw new ReadOnlyPropertyException("Cannot write proxy: " + this); } if (!target.getVersionSeriesId().equals(getVersionSeriesId())) { throw new ReadOnlyPropertyException("Cannot set proxy target to different version series"); } session.setProxyTarget(this, target); } else { throw new NuxeoException("Cannot set proxy target on non-proxy"); } } @Override protected Lock getDocumentLock() { String owner = (String) docState.get(KEY_LOCK_OWNER); if (owner == null) { return null; } Calendar created = (Calendar) docState.get(KEY_LOCK_CREATED); return new Lock(owner, created); } @Override protected Lock setDocumentLock(Lock lock) { String owner = (String) docState.get(KEY_LOCK_OWNER); if (owner != null) { // return old lock Calendar created = (Calendar) docState.get(KEY_LOCK_CREATED); return new Lock(owner, created); } docState.put(KEY_LOCK_OWNER, lock.getOwner()); docState.put(KEY_LOCK_CREATED, lock.getCreated()); return null; } @Override protected Lock removeDocumentLock(String owner) { String oldOwner = (String) docState.get(KEY_LOCK_OWNER); if (oldOwner == null) { // no previous lock return null; } Calendar oldCreated = (Calendar) docState.get(KEY_LOCK_CREATED); if (!LockManager.canLockBeRemoved(oldOwner, owner)) { // existing mismatched lock, flag failure return new Lock(oldOwner, oldCreated, true); } // remove lock docState.put(KEY_LOCK_OWNER, null); docState.put(KEY_LOCK_CREATED, null); // return old lock return new Lock(oldOwner, oldCreated); } @Override public String toString() { return getClass().getSimpleName() + '(' + getName() + ',' + getUUID() + ')'; } @Override public boolean equals(Object other) { if (other == this) { return true; } if (other == null) { return false; } if (other.getClass() == getClass()) { return equals((DBSDocument) other); } return false; } private boolean equals(DBSDocument other) { return id.equals(other.id); } @Override public int hashCode() { return id.hashCode(); } }