001/*
002 * Copyright (c) 2006-2014 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 *
009 * Contributors:
010 *     Florent Guillaume
011 */
012package org.nuxeo.ecm.core.storage.sql.coremodel;
013
014import java.io.Serializable;
015import java.util.Calendar;
016import java.util.Collection;
017import java.util.List;
018import java.util.Map;
019import java.util.Set;
020import java.util.function.Consumer;
021
022import org.nuxeo.ecm.core.api.LifeCycleException;
023import org.nuxeo.ecm.core.api.Lock;
024import org.nuxeo.ecm.core.api.PropertyException;
025import org.nuxeo.ecm.core.api.model.DocumentPart;
026import org.nuxeo.ecm.core.api.model.PropertyNotFoundException;
027import org.nuxeo.ecm.core.api.model.ReadOnlyPropertyException;
028import org.nuxeo.ecm.core.model.Document;
029import org.nuxeo.ecm.core.model.Session;
030import org.nuxeo.ecm.core.schema.DocumentType;
031import org.nuxeo.ecm.core.schema.SchemaManager;
032import org.nuxeo.ecm.core.schema.types.ComplexType;
033import org.nuxeo.ecm.core.schema.types.Schema;
034import org.nuxeo.ecm.core.storage.sql.Model;
035import org.nuxeo.ecm.core.storage.sql.Node;
036import org.nuxeo.runtime.api.Framework;
037
038/**
039 * A proxy is a shortcut to a target document (a version or normal document).
040 */
041public class SQLDocumentProxy implements SQLDocument {
042
043    /** The proxy seen as a normal doc ({@link SQLDocument}). */
044    private final Document proxy;
045
046    /** The target. */
047    private Document target;
048
049    // private SQLDocumentVersion version;
050
051    protected SQLDocumentProxy(Document proxy, Document target) {
052        this.proxy = proxy;
053        this.target = target;
054    }
055
056    protected String getSchema(String xpath) {
057        int p = xpath.indexOf(':');
058        if (p == -1) {
059            throw new PropertyNotFoundException(xpath, "Schema not specified");
060        }
061        String prefix = xpath.substring(0, p);
062        SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class);
063        Schema schema = schemaManager.getSchemaFromPrefix(prefix);
064        if (schema == null) {
065            schema = schemaManager.getSchema(prefix);
066            if (schema == null) {
067                throw new PropertyNotFoundException(xpath, "No schema for prefix");
068            }
069        }
070        return schema.getName();
071    }
072
073    /**
074     * Checks if the given schema should be resolved on the proxy or the target.
075     */
076    protected boolean isSchemaForProxy(String schema) {
077        SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class);
078        return schemaManager.isProxySchema(schema, getType().getName());
079    }
080
081    /**
082     * Checks if the given property should be resolved on the proxy or the target.
083     */
084    protected boolean isPropertyForProxy(String xpath) {
085        if (Model.MAIN_MINOR_VERSION_PROP.equals(xpath) || Model.MAIN_MAJOR_VERSION_PROP.equals(xpath)) {
086            return false;
087        }
088        return isSchemaForProxy(getSchema(xpath));
089    }
090
091    /*
092     * ----- SQLDocument -----
093     */
094
095    @Override
096    public Node getNode() {
097        return ((SQLDocument) proxy).getNode();
098    }
099
100    /*
101     * ----- Document -----
102     */
103
104    @Override
105    public boolean isProxy() {
106        return true;
107    }
108
109    @Override
110    public String getUUID() {
111        return proxy.getUUID();
112    }
113
114    @Override
115    public String getName() {
116        return proxy.getName();
117    }
118
119    @Override
120    public Long getPos() {
121        return proxy.getPos();
122    }
123
124    @Override
125    public Document getParent() {
126        return proxy.getParent();
127    }
128
129    @Override
130    public String getPath() {
131        return proxy.getPath();
132    }
133
134    @Override
135    public void remove() {
136        proxy.remove();
137    }
138
139    @Override
140    public DocumentType getType() {
141        return target.getType();
142    }
143
144    @Override
145    public String getRepositoryName() {
146        return target.getRepositoryName();
147    }
148
149    @Override
150    public Session getSession() {
151        return target.getSession();
152    }
153
154    @Override
155    public boolean isFolder() {
156        return target.isFolder();
157    }
158
159    @Override
160    public void setReadOnly(boolean readonly) {
161        target.setReadOnly(readonly);
162    }
163
164    @Override
165    public boolean isReadOnly() {
166        return target.isReadOnly();
167    }
168
169    @Override
170    public void readDocumentPart(DocumentPart dp) throws PropertyException {
171        if (isSchemaForProxy(dp.getName())) {
172            proxy.readDocumentPart(dp);
173        } else {
174            target.readDocumentPart(dp);
175        }
176    }
177
178    @Override
179    public Map<String, Serializable> readPrefetch(ComplexType complexType, Set<String> xpaths)
180            throws PropertyException {
181        if (isSchemaForProxy(complexType.getName())) {
182            return proxy.readPrefetch(complexType, xpaths);
183        } else {
184            return target.readPrefetch(complexType, xpaths);
185        }
186    }
187
188    @Override
189    public WriteContext getWriteContext() {
190        // proxy or target doesn't matter, this is about typing
191        return proxy.getWriteContext();
192    }
193
194    @Override
195    public boolean writeDocumentPart(DocumentPart dp, WriteContext writeContext) throws PropertyException {
196        if (isSchemaForProxy(dp.getName())) {
197            return proxy.writeDocumentPart(dp, writeContext);
198        } else {
199            return target.writeDocumentPart(dp, writeContext);
200        }
201    }
202
203    @Override
204    public void setSystemProp(String name, Serializable value) {
205        target.setSystemProp(name, value);
206    }
207
208    @Override
209    public <T extends Serializable> T getSystemProp(String name, Class<T> type) {
210        return target.getSystemProp(name, type);
211    }
212
213    @Override
214    public Set<String> getAllFacets() {
215        return target.getAllFacets(); // TODO proxy facets
216    }
217
218    @Override
219    public String[] getFacets() {
220        return target.getFacets(); // TODO proxy facets
221    }
222
223    @Override
224    public boolean hasFacet(String facet) {
225        return target.hasFacet(facet); // TODO proxy facets
226    }
227
228    @Override
229    public boolean addFacet(String facet) {
230        return target.addFacet(facet); // TODO proxy facets
231    }
232
233    @Override
234    public boolean removeFacet(String facet) {
235        return target.removeFacet(facet); // TODO proxy facets
236    }
237
238    /*
239     * ----- LifeCycle -----
240     */
241
242    @Override
243    public String getLifeCyclePolicy() {
244        return target.getLifeCyclePolicy();
245    }
246
247    @Override
248    public void setLifeCyclePolicy(String policy) {
249        target.setLifeCyclePolicy(policy);
250    }
251
252    @Override
253    public String getLifeCycleState() {
254        return target.getLifeCycleState();
255    }
256
257    @Override
258    public void setCurrentLifeCycleState(String state) {
259        target.setCurrentLifeCycleState(state);
260    }
261
262    @Override
263    public void followTransition(String transition) throws LifeCycleException {
264        target.followTransition(transition);
265    }
266
267    @Override
268    public Collection<String> getAllowedStateTransitions() {
269        return target.getAllowedStateTransitions();
270    }
271
272    @Override
273    public Lock getLock() {
274        return target.getLock();
275    }
276
277    @Override
278    public Lock setLock(Lock lock) {
279        return target.setLock(lock);
280    }
281
282    @Override
283    public Lock removeLock(String owner) {
284        return target.removeLock(owner);
285    }
286
287    @Override
288    public boolean isVersion() {
289        return false;
290    }
291
292    @Override
293    public Document getBaseVersion() {
294        return null;
295    }
296
297    @Override
298    public String getVersionSeriesId() {
299        return target.getVersionSeriesId();
300    }
301
302    @Override
303    public Document getSourceDocument() {
304        // this is what the rest of Nuxeo expects for a proxy
305        return target;
306    }
307
308    @Override
309    public Document checkIn(String label, String checkinComment) {
310        return target.checkIn(label, checkinComment);
311    }
312
313    @Override
314    public void checkOut() {
315        target.checkOut();
316    }
317
318    @Override
319    public boolean isCheckedOut() {
320        return target.isCheckedOut();
321    }
322
323    @Override
324    public boolean isLatestVersion() {
325        return target.isLatestVersion();
326    }
327
328    @Override
329    public boolean isMajorVersion() {
330        return target.isMajorVersion();
331    }
332
333    @Override
334    public boolean isLatestMajorVersion() {
335        return target.isLatestMajorVersion();
336    }
337
338    @Override
339    public boolean isVersionSeriesCheckedOut() {
340        return target.isVersionSeriesCheckedOut();
341    }
342
343    @Override
344    public String getVersionLabel() {
345        return target.getVersionLabel();
346    }
347
348    @Override
349    public String getCheckinComment() {
350        return target.getCheckinComment();
351    }
352
353    @Override
354    public Document getWorkingCopy() {
355        return target.getWorkingCopy();
356    }
357
358    @Override
359    public Calendar getVersionCreationDate() {
360        return target.getVersionCreationDate();
361    }
362
363    @Override
364    public void restore(Document version) {
365        target.restore(version);
366    }
367
368    @Override
369    public List<String> getVersionsIds() {
370        return target.getVersionsIds();
371    }
372
373    @Override
374    public Document getVersion(String label) {
375        return target.getVersion(label);
376    }
377
378    @Override
379    public List<Document> getVersions() {
380        return target.getVersions();
381    }
382
383    @Override
384    public Document getLastVersion() {
385        return target.getLastVersion();
386    }
387
388    @Override
389    public Document getChild(String name) {
390        return proxy.getChild(name);
391    }
392
393    @Override
394    public List<Document> getChildren() {
395        return proxy.getChildren();
396    }
397
398    @Override
399    public List<String> getChildrenIds() {
400        return proxy.getChildrenIds();
401    }
402
403    @Override
404    public boolean hasChild(String name) {
405        return proxy.hasChild(name);
406    }
407
408    @Override
409    public boolean hasChildren() {
410        return proxy.hasChildren();
411    }
412
413    @Override
414    public Document addChild(String name, String typeName) {
415        return proxy.addChild(name, typeName);
416    }
417
418    @Override
419    public void orderBefore(String src, String dest) {
420        proxy.orderBefore(src, dest);
421    }
422
423    /*
424     * ----- DocumentProxy -----
425     */
426
427    @Override
428    public Document getTargetDocument() {
429        return target;
430    }
431
432    @Override
433    public void setTargetDocument(Document target) {
434        if (((SQLDocumentLive) proxy).isReadOnly()) {
435            throw new ReadOnlyPropertyException("Cannot write proxy: " + this);
436        }
437        if (!target.getVersionSeriesId().equals(getVersionSeriesId())) {
438            throw new ReadOnlyPropertyException("Cannot set proxy target to different version series");
439        }
440        getSession().setProxyTarget(proxy, target);
441        this.target = target;
442    }
443
444    @Override
445    public Serializable getPropertyValue(String name) {
446        if (isPropertyForProxy(name)) {
447            return proxy.getPropertyValue(name);
448        } else {
449            return target.getPropertyValue(name);
450        }
451    }
452
453    @Override
454    public void setPropertyValue(String name, Serializable value) {
455        if (isPropertyForProxy(name)) {
456            proxy.setPropertyValue(name, value);
457        } else {
458            target.setPropertyValue(name, value);
459        }
460    }
461
462    @Override
463    public Object getValue(String xpath) throws PropertyException {
464        if (isPropertyForProxy(xpath)) {
465            return proxy.getValue(xpath);
466        } else {
467            return target.getValue(xpath);
468        }
469    }
470
471    @Override
472    public void setValue(String xpath, Object value) throws PropertyException {
473        if (isPropertyForProxy(xpath)) {
474            proxy.setValue(xpath, value);
475        } else {
476            target.setValue(xpath, value);
477        }
478    }
479
480    @Override
481    public void visitBlobs(Consumer<BlobAccessor> blobVisitor) throws PropertyException {
482        // visit all blobs from the proxy AND the target
483        proxy.visitBlobs(blobVisitor);
484        target.visitBlobs(blobVisitor);
485    }
486
487    /*
488     * ----- toString/equals/hashcode -----
489     */
490
491    @Override
492    public String toString() {
493        return getClass().getSimpleName() + '(' + target + ',' + proxy.getUUID() + ')';
494    }
495
496    @Override
497    public boolean equals(Object other) {
498        if (other == this) {
499            return true;
500        }
501        if (other instanceof SQLDocumentProxy) {
502            return equals((SQLDocumentProxy) other);
503        }
504        return false;
505    }
506
507    private boolean equals(SQLDocumentProxy other) {
508        return proxy.equals(other.proxy) && target.equals(other.target);
509    }
510
511    @Override
512    public int hashCode() {
513        return proxy.hashCode() + target.hashCode();
514    }
515
516}