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