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.ArrayList;
016import java.util.Calendar;
017import java.util.Collection;
018import java.util.Collections;
019import java.util.HashMap;
020import java.util.List;
021import java.util.Map;
022import java.util.Set;
023import java.util.function.Consumer;
024
025import org.nuxeo.ecm.core.NXCore;
026import org.nuxeo.ecm.core.api.DocumentNotFoundException;
027import org.nuxeo.ecm.core.api.LifeCycleException;
028import org.nuxeo.ecm.core.api.Lock;
029import org.nuxeo.ecm.core.api.NuxeoException;
030import org.nuxeo.ecm.core.api.PropertyException;
031import org.nuxeo.ecm.core.api.model.DocumentPart;
032import org.nuxeo.ecm.core.api.model.Property;
033import org.nuxeo.ecm.core.api.model.PropertyNotFoundException;
034import org.nuxeo.ecm.core.api.model.impl.ComplexProperty;
035import org.nuxeo.ecm.core.blob.BlobManager;
036import org.nuxeo.ecm.core.lifecycle.LifeCycle;
037import org.nuxeo.ecm.core.lifecycle.LifeCycleService;
038import org.nuxeo.ecm.core.model.Document;
039import org.nuxeo.ecm.core.schema.DocumentType;
040import org.nuxeo.ecm.core.schema.SchemaManager;
041import org.nuxeo.ecm.core.schema.types.ComplexType;
042import org.nuxeo.ecm.core.schema.types.Field;
043import org.nuxeo.ecm.core.schema.types.ListType;
044import org.nuxeo.ecm.core.schema.types.Schema;
045import org.nuxeo.ecm.core.schema.types.Type;
046import org.nuxeo.ecm.core.storage.BaseDocument;
047import org.nuxeo.ecm.core.storage.sql.Model;
048import org.nuxeo.ecm.core.storage.sql.Node;
049import org.nuxeo.runtime.api.Framework;
050
051public class SQLDocumentLive extends BaseDocument<Node>implements SQLDocument {
052
053    protected final Node node;
054
055    protected final Type type;
056
057    protected SQLSession session;
058
059    /** Proxy-induced types. */
060    protected final List<Schema> proxySchemas;
061
062    /**
063     * Read-only flag, used to allow/disallow writes on versions.
064     */
065    protected boolean readonly;
066
067    protected SQLDocumentLive(Node node, ComplexType type, SQLSession session, boolean readonly) {
068        this.node = node;
069        this.type = type;
070        this.session = session;
071        if (node != null && node.isProxy()) {
072            SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class);
073            proxySchemas = schemaManager.getProxySchemas(type.getName());
074        } else {
075            proxySchemas = null;
076        }
077        this.readonly = readonly;
078    }
079
080    @Override
081    public void setReadOnly(boolean readonly) {
082        this.readonly = readonly;
083    }
084
085    @Override
086    public boolean isReadOnly() {
087        return readonly;
088    }
089
090    @Override
091    public Node getNode() {
092        return node;
093    }
094
095    @Override
096    public String getName() {
097        return getNode() == null ? null : getNode().getName();
098    }
099
100    @Override
101    public Long getPos() {
102        return getNode().getPos();
103    }
104
105    /*
106     * ----- org.nuxeo.ecm.core.model.Document -----
107     */
108
109    @Override
110    public DocumentType getType() {
111        return (DocumentType) type;
112    }
113
114    @Override
115    public SQLSession getSession() {
116        return session;
117    }
118
119    @Override
120    public boolean isFolder() {
121        return type == null // null document
122                || ((DocumentType) type).isFolder();
123    }
124
125    @Override
126    public String getUUID() {
127        return session.idToString(getNode().getId());
128    }
129
130    @Override
131    public Document getParent() {
132        return session.getParent(getNode());
133    }
134
135    @Override
136    public String getPath() {
137        return session.getPath(getNode());
138    }
139
140    @Override
141    public boolean isProxy() {
142        return false;
143    }
144
145    @Override
146    public String getRepositoryName() {
147        return session.getRepositoryName();
148    }
149
150    @Override
151    protected List<Schema> getProxySchemas() {
152        return proxySchemas;
153    }
154
155    @Override
156    public void remove() {
157        session.remove(getNode());
158    }
159
160    /**
161     * Reads into the {@link DocumentPart} the values from this {@link SQLDocument}.
162     */
163    @Override
164    public void readDocumentPart(DocumentPart dp) throws PropertyException {
165        readComplexProperty(getNode(), (ComplexProperty) dp);
166    }
167
168    @Override
169    public Map<String, Serializable> readPrefetch(ComplexType complexType, Set<String> xpaths)
170            throws PropertyException {
171        return readPrefetch(getNode(), complexType, xpaths);
172    }
173
174    @Override
175    public boolean writeDocumentPart(DocumentPart dp, WriteContext writeContext) throws PropertyException {
176        boolean changed = writeComplexProperty(getNode(), (ComplexProperty) dp, writeContext);
177        clearDirtyFlags(dp);
178        return changed;
179    }
180
181    @Override
182    protected Node getChild(Node node, String name, Type type) throws PropertyException {
183        return session.getChildProperty(node, name, type.getName());
184    }
185
186    @Override
187    protected Node getChildForWrite(Node node, String name, Type type) throws PropertyException {
188        return session.getChildPropertyForWrite(node, name, type.getName());
189    }
190
191    @Override
192    protected List<Node> getChildAsList(Node node, String name) throws PropertyException {
193        return session.getComplexList(node, name);
194    }
195
196    @Override
197    protected void updateList(Node node, String name, List<Object> values, Field field) throws PropertyException {
198        List<Node> childNodes = getChildAsList(node, name);
199        int oldSize = childNodes.size();
200        int newSize = values.size();
201        // remove extra list elements
202        if (oldSize > newSize) {
203            for (int i = oldSize - 1; i >= newSize; i--) {
204                session.removeProperty(childNodes.remove(i));
205            }
206        }
207        // add new list elements
208        if (oldSize < newSize) {
209            String typeName = field.getType().getName();
210            for (int i = oldSize; i < newSize; i++) {
211                Node childNode = session.addChildProperty(node, name, Long.valueOf(i), typeName);
212                childNodes.add(childNode);
213            }
214        }
215        // write values
216        int i = 0;
217        for (Object v : values) {
218            Node childNode = childNodes.get(i++);
219            setValueComplex(childNode, field, v);
220        }
221    }
222
223    @Override
224    protected List<Node> updateList(Node node, String name, Property property) throws PropertyException {
225        Collection<Property> properties = property.getChildren();
226        List<Node> childNodes = getChildAsList(node, name);
227        int oldSize = childNodes.size();
228        int newSize = properties.size();
229        // remove extra list elements
230        if (oldSize > newSize) {
231            for (int i = oldSize - 1; i >= newSize; i--) {
232                session.removeProperty(childNodes.remove(i));
233            }
234        }
235        // add new list elements
236        if (oldSize < newSize) {
237            String typeName = ((ListType) property.getType()).getFieldType().getName();
238            for (int i = oldSize; i < newSize; i++) {
239                Node childNode = session.addChildProperty(node, name, Long.valueOf(i), typeName);
240                childNodes.add(childNode);
241            }
242        }
243        return childNodes;
244    }
245
246    @Override
247    protected String internalName(String name) {
248        return name;
249    }
250
251    @Override
252    public Object getValue(String xpath) throws PropertyException {
253        return getValueObject(getNode(), xpath);
254    }
255
256    @Override
257    public void setValue(String xpath, Object value) throws PropertyException {
258        setValueObject(getNode(), xpath, value);
259    }
260
261    @Override
262    public void visitBlobs(Consumer<BlobAccessor> blobVisitor) throws PropertyException {
263        visitBlobs(getNode(), blobVisitor, NO_DIRTY);
264    }
265
266    @Override
267    public Serializable getPropertyValue(String name) {
268        return getNode().getSimpleProperty(name).getValue();
269    }
270
271    @Override
272    public void setPropertyValue(String name, Serializable value) {
273        getNode().setSimpleProperty(name, value);
274    }
275
276    protected static final Map<String, String> systemPropNameMap;
277
278    static {
279        systemPropNameMap = new HashMap<String, String>();
280        systemPropNameMap.put(FULLTEXT_JOBID_SYS_PROP, Model.FULLTEXT_JOBID_PROP);
281    }
282
283    @Override
284    public void setSystemProp(String name, Serializable value) {
285        String propertyName;
286        if (name.startsWith(SIMPLE_TEXT_SYS_PROP)) {
287            propertyName = name.replace(SIMPLE_TEXT_SYS_PROP, Model.FULLTEXT_SIMPLETEXT_PROP);
288        } else if (name.startsWith(BINARY_TEXT_SYS_PROP)) {
289            propertyName = name.replace(BINARY_TEXT_SYS_PROP, Model.FULLTEXT_BINARYTEXT_PROP);
290        } else {
291            propertyName = systemPropNameMap.get(name);
292        }
293        if (propertyName == null) {
294            throw new PropertyNotFoundException(name, "Unknown system property");
295        }
296        setPropertyValue(propertyName, value);
297    }
298
299    @Override
300    @SuppressWarnings("unchecked")
301    public <T extends Serializable> T getSystemProp(String name, Class<T> type) {
302        String propertyName = systemPropNameMap.get(name);
303        if (propertyName == null) {
304            throw new PropertyNotFoundException(name, "Unknown system property");
305        }
306        Serializable value = getPropertyValue(propertyName);
307        if (value == null) {
308            if (type == Boolean.class) {
309                value = Boolean.FALSE;
310            } else if (type == Long.class) {
311                value = Long.valueOf(0);
312            }
313        }
314        return (T) value;
315    }
316
317    /*
318     * ----- LifeCycle -----
319     */
320
321    @Override
322    public String getLifeCyclePolicy() {
323        return (String) getPropertyValue(Model.MISC_LIFECYCLE_POLICY_PROP);
324    }
325
326    @Override
327    public void setLifeCyclePolicy(String policy) {
328        setPropertyValue(Model.MISC_LIFECYCLE_POLICY_PROP, policy);
329        BlobManager blobManager = Framework.getService(BlobManager.class);
330        blobManager.notifyChanges(this, Collections.singleton(Model.MISC_LIFECYCLE_POLICY_PROP));
331    }
332
333    @Override
334    public String getLifeCycleState() {
335        return (String) getPropertyValue(Model.MISC_LIFECYCLE_STATE_PROP);
336    }
337
338    @Override
339    public void setCurrentLifeCycleState(String state) {
340        setPropertyValue(Model.MISC_LIFECYCLE_STATE_PROP, state);
341        BlobManager blobManager = Framework.getService(BlobManager.class);
342        blobManager.notifyChanges(this, Collections.singleton(Model.MISC_LIFECYCLE_STATE_PROP));
343    }
344
345    @Override
346    public void followTransition(String transition) throws LifeCycleException {
347        LifeCycleService service = NXCore.getLifeCycleService();
348        if (service == null) {
349            throw new NuxeoException("LifeCycleService not available");
350        }
351        service.followTransition(this, transition);
352    }
353
354    @Override
355    public Collection<String> getAllowedStateTransitions() {
356        LifeCycleService service = NXCore.getLifeCycleService();
357        if (service == null) {
358            throw new NuxeoException("LifeCycleService not available");
359        }
360        LifeCycle lifeCycle = service.getLifeCycleFor(this);
361        if (lifeCycle == null) {
362            return Collections.emptyList();
363        }
364        return lifeCycle.getAllowedStateTransitionsFrom(getLifeCycleState());
365    }
366
367    /*
368     * ----- org.nuxeo.ecm.core.versioning.VersionableDocument -----
369     */
370
371    @Override
372    public boolean isVersion() {
373        return false;
374    }
375
376    @Override
377    public Document getBaseVersion() {
378        if (isCheckedOut()) {
379            return null;
380        }
381        Serializable id = (Serializable) getPropertyValue(Model.MAIN_BASE_VERSION_PROP);
382        if (id == null) {
383            // shouldn't happen
384            return null;
385        }
386        return session.getDocumentById(id);
387    }
388
389    @Override
390    public String getVersionSeriesId() {
391        return getUUID();
392    }
393
394    @Override
395    public Document getSourceDocument() {
396        return this;
397    }
398
399    @Override
400    public Document checkIn(String label, String checkinComment) {
401        Document version = session.checkIn(getNode(), label, checkinComment);
402        Framework.getService(BlobManager.class).freezeVersion(version);
403        return version;
404    }
405
406    @Override
407    public void checkOut() {
408        session.checkOut(getNode());
409    }
410
411    @Override
412    public boolean isCheckedOut() {
413        return !Boolean.TRUE.equals(getPropertyValue(Model.MAIN_CHECKED_IN_PROP));
414    }
415
416    @Override
417    public boolean isMajorVersion() {
418        return false;
419    }
420
421    @Override
422    public boolean isLatestVersion() {
423        return false;
424    }
425
426    @Override
427    public boolean isLatestMajorVersion() {
428        return false;
429    }
430
431    @Override
432    public boolean isVersionSeriesCheckedOut() {
433        return isCheckedOut();
434    }
435
436    @Override
437    public String getVersionLabel() {
438        return (String) getPropertyValue(Model.VERSION_LABEL_PROP);
439    }
440
441    @Override
442    public String getCheckinComment() {
443        return (String) getPropertyValue(Model.VERSION_DESCRIPTION_PROP);
444    }
445
446    @Override
447    public Document getWorkingCopy() {
448        return this;
449    }
450
451    @Override
452    public Calendar getVersionCreationDate() {
453        return (Calendar) getPropertyValue(Model.VERSION_CREATED_PROP);
454    }
455
456    @Override
457    public void restore(Document version) {
458        if (!version.isVersion()) {
459            throw new NuxeoException("Cannot restore a non-version: " + version);
460        }
461        session.restore(getNode(), ((SQLDocument) version).getNode());
462    }
463
464    @Override
465    public List<String> getVersionsIds() {
466        String versionSeriesId = getVersionSeriesId();
467        Collection<Document> versions = session.getVersions(versionSeriesId);
468        List<String> ids = new ArrayList<String>(versions.size());
469        for (Document version : versions) {
470            ids.add(version.getUUID());
471        }
472        return ids;
473    }
474
475    @Override
476    public Document getVersion(String label) {
477        String versionSeriesId = getVersionSeriesId();
478        return session.getVersionByLabel(versionSeriesId, label);
479    }
480
481    @Override
482    public List<Document> getVersions() {
483        String versionSeriesId = getVersionSeriesId();
484        return session.getVersions(versionSeriesId);
485    }
486
487    @Override
488    public Document getLastVersion() {
489        String versionSeriesId = getVersionSeriesId();
490        return session.getLastVersion(versionSeriesId);
491    }
492
493    @Override
494    public Document getChild(String name) {
495        return session.getChild(getNode(), name);
496    }
497
498    @Override
499    public List<Document> getChildren() {
500        if (!isFolder()) {
501            return Collections.emptyList();
502        }
503        return session.getChildren(getNode()); // newly allocated
504    }
505
506    @Override
507    public List<String> getChildrenIds() {
508        if (!isFolder()) {
509            return Collections.emptyList();
510        }
511        // not optimized as this method doesn't seem to be used
512        List<Document> children = session.getChildren(getNode());
513        List<String> ids = new ArrayList<String>(children.size());
514        for (Document child : children) {
515            ids.add(child.getUUID());
516        }
517        return ids;
518    }
519
520    @Override
521    public boolean hasChild(String name) {
522        if (!isFolder()) {
523            return false;
524        }
525        return session.hasChild(getNode(), name);
526    }
527
528    @Override
529    public boolean hasChildren() {
530        if (!isFolder()) {
531            return false;
532        }
533        return session.hasChildren(getNode());
534    }
535
536    @Override
537    public Document addChild(String name, String typeName) {
538        if (!isFolder()) {
539            throw new IllegalArgumentException("Not a folder");
540        }
541        return session.addChild(getNode(), name, null, typeName);
542    }
543
544    @Override
545    public void orderBefore(String src, String dest) {
546        SQLDocument srcDoc = (SQLDocument) getChild(src);
547        if (srcDoc == null) {
548            throw new DocumentNotFoundException("Document " + this + " has no child: " + src);
549        }
550        SQLDocument destDoc;
551        if (dest == null) {
552            destDoc = null;
553        } else {
554            destDoc = (SQLDocument) getChild(dest);
555            if (destDoc == null) {
556                throw new DocumentNotFoundException("Document " + this + " has no child: " + dest);
557            }
558        }
559        session.orderBefore(getNode(), srcDoc.getNode(), destDoc == null ? null : destDoc.getNode());
560    }
561
562    @Override
563    public Set<String> getAllFacets() {
564        return getNode().getAllMixinTypes();
565    }
566
567    @Override
568    public String[] getFacets() {
569        return getNode().getMixinTypes();
570    }
571
572    @Override
573    public boolean hasFacet(String facet) {
574        return getNode().hasMixinType(facet);
575    }
576
577    @Override
578    public boolean addFacet(String facet) {
579        return session.addMixinType(getNode(), facet);
580    }
581
582    @Override
583    public boolean removeFacet(String facet) {
584        return session.removeMixinType(getNode(), facet);
585    }
586
587    /*
588     * ----- PropertyContainer inherited from SQLComplexProperty -----
589     */
590
591    /*
592     * ----- toString/equals/hashcode -----
593     */
594
595    @Override
596    public String toString() {
597        return getClass().getSimpleName() + '(' + getName() + ',' + getUUID() + ')';
598    }
599
600    @Override
601    public boolean equals(Object other) {
602        if (other == this) {
603            return true;
604        }
605        if (other == null) {
606            return false;
607        }
608        if (other.getClass() == this.getClass()) {
609            return equals((SQLDocumentLive) other);
610        }
611        return false;
612    }
613
614    private boolean equals(SQLDocumentLive other) {
615        return getNode().equals(other.getNode());
616    }
617
618    @Override
619    public int hashCode() {
620        return getNode().hashCode();
621    }
622
623    @Override
624    public Document getTargetDocument() {
625        return null;
626    }
627
628    @Override
629    public void setTargetDocument(Document target) {
630        throw new NuxeoException("Not a proxy");
631    }
632
633    @Override
634    protected Lock getDocumentLock() {
635        // lock manager can get the lock even on a recently created and unsaved document
636        throw new UnsupportedOperationException();
637    }
638
639    @Override
640    protected Lock setDocumentLock(Lock lock) {
641        // lock manager can set the lock even on a recently created and unsaved document
642        throw new UnsupportedOperationException();
643    }
644
645    @Override
646    protected Lock removeDocumentLock(String owner) {
647        // lock manager can remove the lock even on a recently created and unsaved document
648        throw new UnsupportedOperationException();
649    }
650
651}