001/*
002 * (C) Copyright 2006-2020 Nuxeo (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 *     Thomas Roger
018 *     Dragos Mihalache
019 *     Florent Guillaume
020 */
021package org.nuxeo.ecm.core.api.impl;
022
023import static org.nuxeo.ecm.core.schema.types.ComplexTypeImpl.canonicalXPath;
024
025import java.io.Serializable;
026import java.util.Arrays;
027import java.util.Collection;
028import java.util.Collections;
029import java.util.HashMap;
030import java.util.HashSet;
031import java.util.List;
032import java.util.Map;
033import java.util.Set;
034import java.util.stream.Collectors;
035import java.util.stream.Stream;
036
037import org.nuxeo.common.utils.Path;
038import org.nuxeo.ecm.core.api.CoreSession;
039import org.nuxeo.ecm.core.api.DataModel;
040import org.nuxeo.ecm.core.api.DocumentModel;
041import org.nuxeo.ecm.core.api.DocumentRef;
042import org.nuxeo.ecm.core.api.LifeCycleConstants;
043import org.nuxeo.ecm.core.api.Lock;
044import org.nuxeo.ecm.core.api.NuxeoPrincipal;
045import org.nuxeo.ecm.core.api.PathRef;
046import org.nuxeo.ecm.core.api.PropertyException;
047import org.nuxeo.ecm.core.api.VersioningOption;
048import org.nuxeo.ecm.core.api.model.DocumentPart;
049import org.nuxeo.ecm.core.api.model.Property;
050import org.nuxeo.ecm.core.api.model.PropertyNotFoundException;
051import org.nuxeo.ecm.core.api.model.PropertyVisitor;
052import org.nuxeo.ecm.core.api.model.impl.DocumentPartImpl;
053import org.nuxeo.ecm.core.api.model.resolver.DocumentPropertyObjectResolverImpl;
054import org.nuxeo.ecm.core.api.model.resolver.PropertyObjectResolver;
055import org.nuxeo.ecm.core.api.security.ACP;
056import org.nuxeo.ecm.core.schema.DocumentType;
057import org.nuxeo.ecm.core.schema.SchemaManager;
058import org.nuxeo.ecm.core.schema.types.CompositeType;
059import org.nuxeo.ecm.core.schema.types.Schema;
060import org.nuxeo.runtime.api.Framework;
061
062/**
063 * A DocumentModel that can have any schema and is not made persistent by itself. A mockup to keep arbitrary schema
064 * data.
065 */
066public class SimpleDocumentModel implements DocumentModel {
067
068    private static final long serialVersionUID = 1L;
069
070    protected final Map<String, DataModel> dataModels = new HashMap<>();
071
072    protected final Set<String> schemas = new HashSet<>();
073
074    protected final Set<String> facets = new HashSet<>();
075
076    protected final Map<String, Serializable> contextData = new HashMap<>();
077
078    protected final boolean anySchema;
079
080    protected Path path;
081
082    protected String type;
083
084    /**
085     * @deprecated since 11.1. Use {@link #empty()} instead.
086     */
087    @Deprecated(since = "11.1")
088    public SimpleDocumentModel() {
089        anySchema = true;
090    }
091
092    /**
093     * Returns a new empty {@link SimpleDocumentModel} instance.
094     *
095     * @since 11.1
096     */
097    public static SimpleDocumentModel empty() {
098        return new SimpleDocumentModel();
099    }
100
101    /**
102     * @since 11.1
103     */
104    protected SimpleDocumentModel(DocumentType documentType) {
105        anySchema = false;
106        type = documentType.getName();
107        initSchemas(List.of(documentType.getSchemaNames()));
108    }
109
110    /**
111     * Returns a {@link SimpleDocumentModel} instance initialized with the given {@code type} and its related schemas.
112     *
113     * @since 11.1
114     */
115    public static SimpleDocumentModel ofType(String type) {
116        SchemaManager service = Framework.getService(SchemaManager.class);
117        DocumentType dType = service.getDocumentType(type);
118        return new SimpleDocumentModel(dType);
119    }
120
121    /**
122     * @deprecated since 11.1. Use {@link #ofSchemas(List)} instead.
123     */
124    @Deprecated(since = "11.1")
125    public SimpleDocumentModel(List<String> schemas) {
126        anySchema = false;
127        initSchemas(schemas);
128    }
129
130    /**
131     * Returns a {@link SimpleDocumentModel} instance initialized with the given {@code schemas}.
132     *
133     * @since 11.1
134     */
135    public static SimpleDocumentModel ofSchemas(List<String> schemas) {
136        return new SimpleDocumentModel(schemas);
137    }
138
139    /**
140     * @deprecated since 11.1. Use {@link #ofSchemas(String, String...)} instead.
141     */
142    @Deprecated(since = "11.1")
143    public SimpleDocumentModel(String... schemas) {
144        this(List.of(schemas));
145    }
146
147    /**
148     * Returns a {@link SimpleDocumentModel} instance initialized with the given {@code schema} and optional
149     * {@code schemas}.
150     *
151     * @since 11.1
152     */
153    public static SimpleDocumentModel ofSchemas(String schema, String... schemas) {
154        return ofSchemas(Stream.concat(Stream.of(schema), Stream.of(schemas)).collect(Collectors.toList()));
155    }
156
157    protected final void initSchemas(List<String> schemas) {
158        SchemaManager schemaManager = Framework.getService(SchemaManager.class);
159        for (String schema : schemas) {
160            Schema s = schemaManager.getSchema(schema);
161            DocumentPart part = new DocumentPartImpl(s);
162            dataModels.put(schema, new DataModelImpl(part));
163            this.schemas.add(schema);
164        }
165    }
166
167    protected DataModel getDataModelInternal(String schema) {
168        DataModel dm = dataModels.get(schema);
169        if (dm == null && anySchema) {
170            SchemaManager schemaManager = Framework.getService(SchemaManager.class);
171            Schema s = schemaManager.getSchema(schema);
172            DocumentPart part = new DocumentPartImpl(s);
173            dm = new DataModelImpl(part);
174            dataModels.put(schema, dm);
175            schemas.add(schema);
176        }
177        return dm;
178    }
179
180    @Override
181    public String[] getSchemas() {
182        Set<String> keys = dataModels.keySet();
183        return keys.toArray(new String[0]);
184    }
185
186    @Override
187    @SuppressWarnings("deprecation")
188    public Object getProperty(String schemaName, String name) {
189        DataModel dm = getDataModelInternal(schemaName);
190        return dm != null ? dm.getData(name) : null;
191    }
192
193    @Override
194    @SuppressWarnings("deprecation")
195    public Property getPropertyObject(String schema, String name) {
196        DocumentPart part = getPart(schema);
197        return part == null ? null : part.get(name);
198    }
199
200    @Override
201    public void setProperty(String schemaName, String name, Object value) {
202        if (name.contains(":")) {
203            name = name.substring(name.indexOf(':'));
204        }
205        getDataModelInternal(schemaName).setData(name, value);
206    }
207
208    @Override
209    public Map<String, Object> getProperties(String schemaName) {
210        return getDataModelInternal(schemaName).getMap();
211    }
212
213    @Override
214    @SuppressWarnings("deprecation")
215    public void setProperties(String schemaName, Map<String, Object> data) {
216        DataModel dm = getDataModelInternal(schemaName);
217        dm.setMap(data);
218        // force dirty for updated properties
219        for (String field : data.keySet()) {
220            dm.setDirty(field);
221        }
222    }
223
224    @Override
225    public Map<String, Serializable> getContextData() {
226        return contextData;
227    }
228
229    @Override
230    public Serializable getContextData(String key) {
231        return contextData.get(key);
232    }
233
234    @Override
235    public void putContextData(String key, Serializable value) {
236        contextData.put(key, value);
237    }
238
239    @Override
240    public void copyContextData(DocumentModel otherDocument) {
241        contextData.putAll(otherDocument.getContextData());
242    }
243
244    @Override
245    @SuppressWarnings("deprecation")
246    public Property getProperty(String xpath) throws PropertyException {
247        if (xpath == null) {
248            throw new PropertyNotFoundException("null", "Invalid null xpath");
249        }
250        String cxpath = canonicalXPath(xpath);
251        if (cxpath.isEmpty()) {
252            throw new PropertyNotFoundException(xpath, "Schema not specified");
253        }
254        String schemaName = DocumentModelImpl.getXPathSchemaName(cxpath, schemas, null);
255        if (schemaName == null) {
256            if (cxpath.indexOf(':') != -1) {
257                throw new PropertyNotFoundException(xpath, "No such schema");
258            } else {
259                throw new PropertyNotFoundException(xpath);
260            }
261
262        }
263        DocumentPart part = getPart(schemaName);
264        if (part == null) {
265            throw new PropertyNotFoundException(xpath);
266        }
267        // cut prefix
268        String partPath = cxpath.substring(cxpath.indexOf(':') + 1);
269        try {
270            Property property = part.resolvePath(partPath);
271            // force dirty for updated properties
272            property.setForceDirty(true);
273            return property;
274        } catch (PropertyNotFoundException e) {
275            throw new PropertyNotFoundException(xpath, e.getDetail());
276        }
277    }
278
279    @Override
280    public Serializable getPropertyValue(String xpath) throws PropertyException {
281        return getProperty(xpath).getValue();
282    }
283
284    @Override
285    public void setPropertyValue(String xpath, Serializable value) {
286        getProperty(xpath).setValue(value);
287    }
288
289    @Override
290    public DocumentType getDocumentType() {
291        throw new UnsupportedOperationException();
292    }
293
294    @Override
295    public String getSessionId() {
296        throw new UnsupportedOperationException();
297    }
298
299    @Override
300    public NuxeoPrincipal getPrincipal() {
301        throw new UnsupportedOperationException();
302    }
303
304    @Override
305    public CoreSession getCoreSession() {
306        throw new UnsupportedOperationException();
307    }
308
309    @Override
310    public void detach(boolean loadAll) {
311    }
312
313    @Override
314    public void attach(CoreSession coreSession) {
315    }
316
317    @Override
318    public boolean isAttached() {
319        return false;
320    }
321
322    @Override
323    public DocumentRef getRef() {
324        throw new UnsupportedOperationException();
325    }
326
327    @Override
328    public DocumentRef getParentRef() {
329        if (path == null) {
330            return null;
331        }
332        if (!path.isAbsolute()) {
333            return null;
334        }
335        return new PathRef(path.removeLastSegments(1).toString());
336    }
337
338    @Override
339    public String getId() {
340        throw new UnsupportedOperationException();
341    }
342
343    @Override
344    public String getName() {
345        return path == null ? null : path.lastSegment();
346    }
347
348    @Override
349    public Long getPos() {
350        return null;
351    }
352
353    @Override
354    public String getPathAsString() {
355        return path == null ? null : path.toString();
356    }
357
358    @Override
359    public Path getPath() {
360        return path;
361    }
362
363    @Override
364    public String getTitle() {
365        throw new UnsupportedOperationException();
366    }
367
368    @Override
369    public String getType() {
370        return type;
371    }
372
373    /**
374     * @deprecated since 11.1. Use {@link #ofType(String)}.
375     */
376    @Deprecated(since = "11.1")
377    public void setType(String type) {
378        this.type = type;
379    }
380
381    @Override
382    public Set<String> getFacets() {
383        throw new UnsupportedOperationException();
384    }
385
386    @Override
387    @Deprecated
388    public Collection<DataModel> getDataModelsCollection() {
389        throw new UnsupportedOperationException();
390    }
391
392    @Override
393    @Deprecated
394    public Map<String, DataModel> getDataModels() {
395        return dataModels;
396    }
397
398    @Override
399    @Deprecated
400    public DataModel getDataModel(String schema) {
401        return getDataModelInternal(schema);
402    }
403
404    @Override
405    public void setPathInfo(String parentPath, String name) {
406        path = new Path(parentPath == null ? name : parentPath + '/' + name);
407    }
408
409    @Override
410    public boolean isLocked() {
411        throw new UnsupportedOperationException();
412    }
413
414    @Override
415    public Lock setLock() {
416        throw new UnsupportedOperationException();
417    }
418
419    @Override
420    public Lock getLockInfo() {
421        throw new UnsupportedOperationException();
422    }
423
424    @Override
425    public Lock removeLock() {
426        throw new UnsupportedOperationException();
427    }
428
429    @Override
430    public ACP getACP() {
431        throw new UnsupportedOperationException();
432    }
433
434    @Override
435    public void setACP(ACP acp, boolean overwrite) {
436        throw new UnsupportedOperationException();
437    }
438
439    @Override
440    public boolean hasSchema(String schema) {
441        throw new UnsupportedOperationException();
442    }
443
444    @Override
445    public boolean hasFacet(String facet) {
446        return facets.contains(facet);
447    }
448
449    @Override
450    public boolean addFacet(String facet) {
451        if (facet == null) {
452            throw new IllegalArgumentException("Null facet");
453        }
454        if (facets.contains(facet)) {
455            return false;
456        }
457        SchemaManager schemaManager = Framework.getService(SchemaManager.class);
458        CompositeType facetType = schemaManager.getFacet(facet);
459        if (facetType == null) {
460            throw new IllegalArgumentException("No such facet: " + facet);
461        }
462        // add it
463        facets.add(facet);
464        schemas.addAll(Arrays.asList(facetType.getSchemaNames()));
465
466        for (Schema schema : facetType.getSchemas()) {
467            DocumentPart part = new DocumentPartImpl(schema);
468            dataModels.put(schema.getName(), new DataModelImpl(part));
469        }
470
471        return true;
472    }
473
474    @Override
475    public boolean removeFacet(String facet) {
476        // not implemented for now because logic is complex as we need to know initial type/schema
477        throw new UnsupportedOperationException();
478    }
479
480    @Override
481    public boolean isTrashed() {
482        return LifeCycleConstants.DELETED_STATE.equals(getCurrentLifeCycleState());
483    }
484
485    @Override
486    public boolean isFolder() {
487        throw new UnsupportedOperationException();
488    }
489
490    @Override
491    public boolean isVersionable() {
492        throw new UnsupportedOperationException();
493    }
494
495    @Override
496    public boolean isDownloadable() {
497        throw new UnsupportedOperationException();
498    }
499
500    @Override
501    public boolean isVersion() {
502        throw new UnsupportedOperationException();
503    }
504
505    @Override
506    public boolean isProxy() {
507        throw new UnsupportedOperationException();
508    }
509
510    @Override
511    public boolean isImmutable() {
512        throw new UnsupportedOperationException();
513    }
514
515    @Override
516    public boolean isDirty() {
517        throw new UnsupportedOperationException();
518    }
519
520    @Override
521    public void accept(PropertyVisitor visitor, Object arg) {
522        throw new UnsupportedOperationException();
523    }
524
525    @Override
526    public <T> T getAdapter(Class<T> itf) {
527        throw new UnsupportedOperationException();
528    }
529
530    @Override
531    public <T> T getAdapter(Class<T> itf, boolean refreshCache) {
532        throw new UnsupportedOperationException();
533    }
534
535    @Override
536    public String getCurrentLifeCycleState() {
537        throw new UnsupportedOperationException();
538    }
539
540    @Override
541    public String getLifeCyclePolicy() {
542        throw new UnsupportedOperationException();
543    }
544
545    @Override
546    public boolean followTransition(String transition) {
547        throw new UnsupportedOperationException();
548    }
549
550    @Override
551    public Collection<String> getAllowedStateTransitions() {
552        throw new UnsupportedOperationException();
553    }
554
555    @Override
556    public void copyContent(DocumentModel sourceDoc) {
557        throw new UnsupportedOperationException();
558    }
559
560    @Override
561    public String getRepositoryName() {
562        throw new UnsupportedOperationException();
563    }
564
565    @Override
566    public String getCacheKey() {
567        throw new UnsupportedOperationException();
568    }
569
570    @Override
571    public String getSourceId() {
572        throw new UnsupportedOperationException();
573    }
574
575    @Override
576    public String getVersionLabel() {
577        throw new UnsupportedOperationException();
578    }
579
580    @Override
581    public String getCheckinComment() {
582        throw new UnsupportedOperationException();
583    }
584
585    @Override
586    public boolean isPrefetched(String xpath) {
587        return false;
588    }
589
590    @Override
591    public boolean isPrefetched(String schemaName, String name) {
592        return false;
593    }
594
595    @Override
596    public void prefetchCurrentLifecycleState(String lifecycle) {
597        throw new UnsupportedOperationException();
598    }
599
600    @Override
601    public void prefetchLifeCyclePolicy(String lifeCyclePolicy) {
602        throw new UnsupportedOperationException();
603    }
604
605    @Override
606    public boolean isLifeCycleLoaded() {
607        throw new UnsupportedOperationException();
608    }
609
610    @Override
611    public <T extends Serializable> T getSystemProp(String systemProperty, Class<T> type) {
612        throw new UnsupportedOperationException();
613    }
614
615    @Override
616    @Deprecated
617    public DocumentPart getPart(String schema) {
618        DataModel dm = getDataModel(schema);
619        if (dm != null) {
620            return ((DataModelImpl) dm).getDocumentPart();
621        }
622        return null;
623    }
624
625    @Override
626    @Deprecated
627    public DocumentPart[] getParts() {
628        throw new UnsupportedOperationException();
629    }
630
631    @Override
632    @SuppressWarnings("deprecation")
633    public Collection<Property> getPropertyObjects(String schema) {
634        DocumentPart part = getPart(schema);
635        return part == null ? Collections.emptyList() : part.getChildren();
636    }
637
638    @Override
639    public void reset() {
640        throw new UnsupportedOperationException();
641    }
642
643    @Override
644    public void refresh(int refreshFlags, String[] schemas) {
645        throw new UnsupportedOperationException();
646    }
647
648    @Override
649    public void refresh() {
650        throw new UnsupportedOperationException();
651    }
652
653    @Override
654    public DocumentModel clone() {
655        throw new UnsupportedOperationException();
656    }
657
658    @Override
659    public boolean isCheckedOut() {
660        throw new UnsupportedOperationException();
661    }
662
663    @Override
664    public void checkOut() {
665        throw new UnsupportedOperationException();
666    }
667
668    @Override
669    public DocumentRef checkIn(VersioningOption option, String description) {
670        throw new UnsupportedOperationException();
671    }
672
673    @Override
674    public String getVersionSeriesId() {
675        throw new UnsupportedOperationException();
676    }
677
678    @Override
679    public boolean isLatestVersion() {
680        return false;
681    }
682
683    @Override
684    public boolean isMajorVersion() {
685        return false;
686    }
687
688    @Override
689    public boolean isLatestMajorVersion() {
690        return false;
691    }
692
693    @Override
694    public boolean isVersionSeriesCheckedOut() {
695        return true;
696    }
697
698    @Override
699    public String getChangeToken() {
700        return null;
701    }
702
703    @Override
704    public Map<String, String> getBinaryFulltext() {
705        return null;
706    }
707
708    @Override
709    public PropertyObjectResolver getObjectResolver(String xpath) {
710        return DocumentPropertyObjectResolverImpl.create(this, xpath);
711    }
712
713}