/* * (C) Copyright 2006-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.sql.coremodel; import java.io.Serializable; import java.util.Calendar; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Consumer; import org.nuxeo.ecm.core.api.LifeCycleException; import org.nuxeo.ecm.core.api.Lock; import org.nuxeo.ecm.core.api.PropertyException; import org.nuxeo.ecm.core.api.model.DocumentPart; import org.nuxeo.ecm.core.api.model.PropertyNotFoundException; import org.nuxeo.ecm.core.api.model.ReadOnlyPropertyException; import org.nuxeo.ecm.core.model.Document; 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.Schema; import org.nuxeo.ecm.core.storage.sql.Model; import org.nuxeo.ecm.core.storage.sql.Node; import org.nuxeo.runtime.api.Framework; /** * A proxy is a shortcut to a target document (a version or normal document). */ public class SQLDocumentProxy implements SQLDocument { /** The proxy seen as a normal doc ({@link SQLDocument}). */ private final Document proxy; /** The target. */ private Document target; // private SQLDocumentVersion version; protected SQLDocumentProxy(Document proxy, Document target) { this.proxy = proxy; this.target = target; } protected String getSchema(String xpath) { int p = xpath.indexOf(':'); if (p == -1) { throw new PropertyNotFoundException(xpath, "Schema not specified"); } String prefix = xpath.substring(0, p); SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class); Schema schema = schemaManager.getSchemaFromPrefix(prefix); if (schema == null) { schema = schemaManager.getSchema(prefix); if (schema == null) { throw new PropertyNotFoundException(xpath, "No schema for prefix"); } } return schema.getName(); } /** * Checks if the given schema should be resolved on the proxy or the target. */ protected boolean isSchemaForProxy(String schema) { SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class); return schemaManager.isProxySchema(schema, getType().getName()); } /** * Checks if the given property should be resolved on the proxy or the target. */ protected boolean isPropertyForProxy(String xpath) { if (Model.MAIN_MINOR_VERSION_PROP.equals(xpath) || Model.MAIN_MAJOR_VERSION_PROP.equals(xpath)) { return false; } return isSchemaForProxy(getSchema(xpath)); } /* * ----- SQLDocument ----- */ @Override public Node getNode() { return ((SQLDocument) proxy).getNode(); } /* * ----- Document ----- */ @Override public boolean isProxy() { return true; } @Override public String getUUID() { return proxy.getUUID(); } @Override public String getName() { return proxy.getName(); } @Override public Long getPos() { return proxy.getPos(); } @Override public Document getParent() { return proxy.getParent(); } @Override public String getPath() { return proxy.getPath(); } @Override public void remove() { proxy.remove(); } @Override public DocumentType getType() { return target.getType(); } @Override public String getRepositoryName() { return target.getRepositoryName(); } @Override public Session getSession() { return target.getSession(); } @Override public boolean isFolder() { return target.isFolder(); } @Override public void setReadOnly(boolean readonly) { target.setReadOnly(readonly); } @Override public boolean isReadOnly() { return target.isReadOnly(); } @Override public void readDocumentPart(DocumentPart dp) throws PropertyException { if (isSchemaForProxy(dp.getName())) { proxy.readDocumentPart(dp); } else { target.readDocumentPart(dp); } } @Override public Map<String, Serializable> readPrefetch(ComplexType complexType, Set<String> xpaths) throws PropertyException { if (isSchemaForProxy(complexType.getName())) { return proxy.readPrefetch(complexType, xpaths); } else { return target.readPrefetch(complexType, xpaths); } } @Override public WriteContext getWriteContext() { // proxy or target doesn't matter, this is about typing return proxy.getWriteContext(); } @Override public boolean writeDocumentPart(DocumentPart dp, WriteContext writeContext) throws PropertyException { if (isSchemaForProxy(dp.getName())) { return proxy.writeDocumentPart(dp, writeContext); } else { return target.writeDocumentPart(dp, writeContext); } } @Override public void setSystemProp(String name, Serializable value) { target.setSystemProp(name, value); } @Override public <T extends Serializable> T getSystemProp(String name, Class<T> type) { return target.getSystemProp(name, type); } @Override public String getChangeToken() { return target.getChangeToken(); // TODO take into account proxy changes as well } @Override public Set<String> getAllFacets() { return target.getAllFacets(); // TODO proxy facets } @Override public String[] getFacets() { return target.getFacets(); // TODO proxy facets } @Override public boolean hasFacet(String facet) { return target.hasFacet(facet); // TODO proxy facets } @Override public boolean addFacet(String facet) { return target.addFacet(facet); // TODO proxy facets } @Override public boolean removeFacet(String facet) { return target.removeFacet(facet); // TODO proxy facets } /* * ----- LifeCycle ----- */ @Override public String getLifeCyclePolicy() { return target.getLifeCyclePolicy(); } @Override public void setLifeCyclePolicy(String policy) { target.setLifeCyclePolicy(policy); } @Override public String getLifeCycleState() { return target.getLifeCycleState(); } @Override public void setCurrentLifeCycleState(String state) { target.setCurrentLifeCycleState(state); } @Override public void followTransition(String transition) throws LifeCycleException { target.followTransition(transition); } @Override public Collection<String> getAllowedStateTransitions() { return target.getAllowedStateTransitions(); } @Override public Lock getLock() { return target.getLock(); } @Override public Lock setLock(Lock lock) { return target.setLock(lock); } @Override public Lock removeLock(String owner) { return target.removeLock(owner); } @Override public boolean isVersion() { return false; } @Override public Document getBaseVersion() { return target.getBaseVersion(); } @Override public String getVersionSeriesId() { return target.getVersionSeriesId(); } @Override public Document getSourceDocument() { // this is what the rest of Nuxeo expects for a proxy return target; } @Override public Document checkIn(String label, String checkinComment) { return target.checkIn(label, checkinComment); } @Override public void checkOut() { target.checkOut(); } @Override public boolean isCheckedOut() { return target.isCheckedOut(); } @Override public boolean isLatestVersion() { return target.isLatestVersion(); } @Override public boolean isMajorVersion() { return target.isMajorVersion(); } @Override public boolean isLatestMajorVersion() { return target.isLatestMajorVersion(); } @Override public boolean isVersionSeriesCheckedOut() { return target.isVersionSeriesCheckedOut(); } @Override public String getVersionLabel() { return target.getVersionLabel(); } @Override public String getCheckinComment() { return target.getCheckinComment(); } @Override public Document getWorkingCopy() { return target.getWorkingCopy(); } @Override public Calendar getVersionCreationDate() { return target.getVersionCreationDate(); } @Override public void restore(Document version) { target.restore(version); } @Override public List<String> getVersionsIds() { return target.getVersionsIds(); } @Override public Document getVersion(String label) { return target.getVersion(label); } @Override public List<Document> getVersions() { return target.getVersions(); } @Override public Document getLastVersion() { return target.getLastVersion(); } @Override public Document getChild(String name) { return proxy.getChild(name); } @Override public List<Document> getChildren() { return proxy.getChildren(); } @Override public List<String> getChildrenIds() { return proxy.getChildrenIds(); } @Override public boolean hasChild(String name) { return proxy.hasChild(name); } @Override public boolean hasChildren() { return proxy.hasChildren(); } @Override public Document addChild(String name, String typeName) { return proxy.addChild(name, typeName); } @Override public void orderBefore(String src, String dest) { proxy.orderBefore(src, dest); } /* * ----- DocumentProxy ----- */ @Override public Document getTargetDocument() { return target; } @Override public void setTargetDocument(Document target) { if (((SQLDocumentLive) proxy).isReadOnly()) { throw new ReadOnlyPropertyException("Cannot write proxy: " + this); } if (!target.getVersionSeriesId().equals(getVersionSeriesId())) { throw new ReadOnlyPropertyException("Cannot set proxy target to different version series"); } getSession().setProxyTarget(proxy, target); this.target = target; } @Override public Serializable getPropertyValue(String name) { if (isPropertyForProxy(name)) { return proxy.getPropertyValue(name); } else { return target.getPropertyValue(name); } } @Override public void setPropertyValue(String name, Serializable value) { if (isPropertyForProxy(name)) { proxy.setPropertyValue(name, value); } else { target.setPropertyValue(name, value); } } @Override public Object getValue(String xpath) throws PropertyException { if (isPropertyForProxy(xpath)) { return proxy.getValue(xpath); } else { return target.getValue(xpath); } } @Override public void setValue(String xpath, Object value) throws PropertyException { if (isPropertyForProxy(xpath)) { proxy.setValue(xpath, value); } else { target.setValue(xpath, value); } } @Override public void visitBlobs(Consumer<BlobAccessor> blobVisitor) throws PropertyException { // visit all blobs from the proxy AND the target proxy.visitBlobs(blobVisitor); target.visitBlobs(blobVisitor); } /* * ----- toString/equals/hashcode ----- */ @Override public String toString() { return getClass().getSimpleName() + '(' + target + ',' + proxy.getUUID() + ')'; } @Override public boolean equals(Object other) { if (other == this) { return true; } if (other instanceof SQLDocumentProxy) { return equals((SQLDocumentProxy) other); } return false; } private boolean equals(SQLDocumentProxy other) { return proxy.equals(other.proxy) && target.equals(other.target); } @Override public int hashCode() { return proxy.hashCode() + target.hashCode(); } }