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 Set<String> getAllFacets() {
222        return target.getAllFacets(); // TODO proxy facets
223    }
224
225    @Override
226    public String[] getFacets() {
227        return target.getFacets(); // TODO proxy facets
228    }
229
230    @Override
231    public boolean hasFacet(String facet) {
232        return target.hasFacet(facet); // TODO proxy facets
233    }
234
235    @Override
236    public boolean addFacet(String facet) {
237        return target.addFacet(facet); // TODO proxy facets
238    }
239
240    @Override
241    public boolean removeFacet(String facet) {
242        return target.removeFacet(facet); // TODO proxy facets
243    }
244
245    /*
246     * ----- LifeCycle -----
247     */
248
249    @Override
250    public String getLifeCyclePolicy() {
251        return target.getLifeCyclePolicy();
252    }
253
254    @Override
255    public void setLifeCyclePolicy(String policy) {
256        target.setLifeCyclePolicy(policy);
257    }
258
259    @Override
260    public String getLifeCycleState() {
261        return target.getLifeCycleState();
262    }
263
264    @Override
265    public void setCurrentLifeCycleState(String state) {
266        target.setCurrentLifeCycleState(state);
267    }
268
269    @Override
270    public void followTransition(String transition) throws LifeCycleException {
271        target.followTransition(transition);
272    }
273
274    @Override
275    public Collection<String> getAllowedStateTransitions() {
276        return target.getAllowedStateTransitions();
277    }
278
279    @Override
280    public Lock getLock() {
281        return target.getLock();
282    }
283
284    @Override
285    public Lock setLock(Lock lock) {
286        return target.setLock(lock);
287    }
288
289    @Override
290    public Lock removeLock(String owner) {
291        return target.removeLock(owner);
292    }
293
294    @Override
295    public boolean isVersion() {
296        return false;
297    }
298
299    @Override
300    public Document getBaseVersion() {
301        return null;
302    }
303
304    @Override
305    public String getVersionSeriesId() {
306        return target.getVersionSeriesId();
307    }
308
309    @Override
310    public Document getSourceDocument() {
311        // this is what the rest of Nuxeo expects for a proxy
312        return target;
313    }
314
315    @Override
316    public Document checkIn(String label, String checkinComment) {
317        return target.checkIn(label, checkinComment);
318    }
319
320    @Override
321    public void checkOut() {
322        target.checkOut();
323    }
324
325    @Override
326    public boolean isCheckedOut() {
327        return target.isCheckedOut();
328    }
329
330    @Override
331    public boolean isLatestVersion() {
332        return target.isLatestVersion();
333    }
334
335    @Override
336    public boolean isMajorVersion() {
337        return target.isMajorVersion();
338    }
339
340    @Override
341    public boolean isLatestMajorVersion() {
342        return target.isLatestMajorVersion();
343    }
344
345    @Override
346    public boolean isVersionSeriesCheckedOut() {
347        return target.isVersionSeriesCheckedOut();
348    }
349
350    @Override
351    public String getVersionLabel() {
352        return target.getVersionLabel();
353    }
354
355    @Override
356    public String getCheckinComment() {
357        return target.getCheckinComment();
358    }
359
360    @Override
361    public Document getWorkingCopy() {
362        return target.getWorkingCopy();
363    }
364
365    @Override
366    public Calendar getVersionCreationDate() {
367        return target.getVersionCreationDate();
368    }
369
370    @Override
371    public void restore(Document version) {
372        target.restore(version);
373    }
374
375    @Override
376    public List<String> getVersionsIds() {
377        return target.getVersionsIds();
378    }
379
380    @Override
381    public Document getVersion(String label) {
382        return target.getVersion(label);
383    }
384
385    @Override
386    public List<Document> getVersions() {
387        return target.getVersions();
388    }
389
390    @Override
391    public Document getLastVersion() {
392        return target.getLastVersion();
393    }
394
395    @Override
396    public Document getChild(String name) {
397        return proxy.getChild(name);
398    }
399
400    @Override
401    public List<Document> getChildren() {
402        return proxy.getChildren();
403    }
404
405    @Override
406    public List<String> getChildrenIds() {
407        return proxy.getChildrenIds();
408    }
409
410    @Override
411    public boolean hasChild(String name) {
412        return proxy.hasChild(name);
413    }
414
415    @Override
416    public boolean hasChildren() {
417        return proxy.hasChildren();
418    }
419
420    @Override
421    public Document addChild(String name, String typeName) {
422        return proxy.addChild(name, typeName);
423    }
424
425    @Override
426    public void orderBefore(String src, String dest) {
427        proxy.orderBefore(src, dest);
428    }
429
430    /*
431     * ----- DocumentProxy -----
432     */
433
434    @Override
435    public Document getTargetDocument() {
436        return target;
437    }
438
439    @Override
440    public void setTargetDocument(Document target) {
441        if (((SQLDocumentLive) proxy).isReadOnly()) {
442            throw new ReadOnlyPropertyException("Cannot write proxy: " + this);
443        }
444        if (!target.getVersionSeriesId().equals(getVersionSeriesId())) {
445            throw new ReadOnlyPropertyException("Cannot set proxy target to different version series");
446        }
447        getSession().setProxyTarget(proxy, target);
448        this.target = target;
449    }
450
451    @Override
452    public Serializable getPropertyValue(String name) {
453        if (isPropertyForProxy(name)) {
454            return proxy.getPropertyValue(name);
455        } else {
456            return target.getPropertyValue(name);
457        }
458    }
459
460    @Override
461    public void setPropertyValue(String name, Serializable value) {
462        if (isPropertyForProxy(name)) {
463            proxy.setPropertyValue(name, value);
464        } else {
465            target.setPropertyValue(name, value);
466        }
467    }
468
469    @Override
470    public Object getValue(String xpath) throws PropertyException {
471        if (isPropertyForProxy(xpath)) {
472            return proxy.getValue(xpath);
473        } else {
474            return target.getValue(xpath);
475        }
476    }
477
478    @Override
479    public void setValue(String xpath, Object value) throws PropertyException {
480        if (isPropertyForProxy(xpath)) {
481            proxy.setValue(xpath, value);
482        } else {
483            target.setValue(xpath, value);
484        }
485    }
486
487    @Override
488    public void visitBlobs(Consumer<BlobAccessor> blobVisitor) throws PropertyException {
489        // visit all blobs from the proxy AND the target
490        proxy.visitBlobs(blobVisitor);
491        target.visitBlobs(blobVisitor);
492    }
493
494    /*
495     * ----- toString/equals/hashcode -----
496     */
497
498    @Override
499    public String toString() {
500        return getClass().getSimpleName() + '(' + target + ',' + proxy.getUUID() + ')';
501    }
502
503    @Override
504    public boolean equals(Object other) {
505        if (other == this) {
506            return true;
507        }
508        if (other instanceof SQLDocumentProxy) {
509            return equals((SQLDocumentProxy) other);
510        }
511        return false;
512    }
513
514    private boolean equals(SQLDocumentProxy other) {
515        return proxy.equals(other.proxy) && target.equals(other.target);
516    }
517
518    @Override
519    public int hashCode() {
520        return proxy.hashCode() + target.hashCode();
521    }
522
523}