001/*
002 * (C) Copyright 2006-2011 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 */
018package org.nuxeo.ecm.core.opencmis.impl.server;
019
020import java.util.HashMap;
021import java.util.Map;
022
023import org.apache.chemistry.opencmis.commons.PropertyIds;
024import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition;
025import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
026import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
027import org.apache.chemistry.opencmis.commons.enums.Cardinality;
028import org.apache.chemistry.opencmis.commons.enums.CmisVersion;
029import org.apache.chemistry.opencmis.commons.enums.ContentStreamAllowed;
030import org.apache.chemistry.opencmis.commons.enums.PropertyType;
031import org.apache.chemistry.opencmis.commons.enums.Updatability;
032import org.apache.chemistry.opencmis.commons.impl.dataobjects.AbstractPropertyDefinition;
033import org.apache.chemistry.opencmis.commons.impl.dataobjects.AbstractTypeDefinition;
034import org.apache.chemistry.opencmis.commons.impl.dataobjects.DocumentTypeDefinitionImpl;
035import org.apache.chemistry.opencmis.commons.impl.dataobjects.FolderTypeDefinitionImpl;
036import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyBooleanDefinitionImpl;
037import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyDateTimeDefinitionImpl;
038import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyDecimalDefinitionImpl;
039import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyHtmlDefinitionImpl;
040import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIdDefinitionImpl;
041import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIntegerDefinitionImpl;
042import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyStringDefinitionImpl;
043import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyUriDefinitionImpl;
044import org.apache.chemistry.opencmis.commons.impl.dataobjects.RelationshipTypeDefinitionImpl;
045import org.apache.chemistry.opencmis.commons.impl.dataobjects.SecondaryTypeDefinitionImpl;
046import org.apache.commons.logging.Log;
047import org.apache.commons.logging.LogFactory;
048import org.nuxeo.common.utils.Path;
049import org.nuxeo.ecm.core.api.DocumentModel;
050import org.nuxeo.ecm.core.api.blobholder.BlobHolder;
051import org.nuxeo.ecm.core.api.impl.DocumentModelImpl;
052import org.nuxeo.ecm.core.schema.DocumentType;
053import org.nuxeo.ecm.core.schema.FacetNames;
054import org.nuxeo.ecm.core.schema.Namespace;
055import org.nuxeo.ecm.core.schema.SchemaManager;
056import org.nuxeo.ecm.core.schema.types.CompositeType;
057import org.nuxeo.ecm.core.schema.types.Field;
058import org.nuxeo.ecm.core.schema.types.ListType;
059import org.nuxeo.ecm.core.schema.types.Schema;
060import org.nuxeo.ecm.core.schema.types.SimpleType;
061import org.nuxeo.ecm.core.schema.types.Type;
062import org.nuxeo.ecm.core.schema.types.primitives.BooleanType;
063import org.nuxeo.ecm.core.schema.types.primitives.DateType;
064import org.nuxeo.ecm.core.schema.types.primitives.DoubleType;
065import org.nuxeo.ecm.core.schema.types.primitives.LongType;
066import org.nuxeo.ecm.core.schema.types.primitives.StringType;
067import org.nuxeo.runtime.api.Framework;
068
069/**
070 * Nuxeo Type Utilities.
071 * <p>
072 * Maps Nuxeo types to CMIS types using the following rules:
073 * <ul>
074 * <li>Only types containing dublincore are exposed,</li>
075 * <li>cmis:document and cmis:folder expose dublincore, and are not creatable,</li>
076 * <li>The Document type is not exposed,</li>
077 * <li>Types inheriting from Document are exposed as inheriting cmis:document,</li>
078 * <li>The Folder type is mapped to a concrete subtype of cmis:folder,</li>
079 * <li>Other folderish types directly under Folder are mapped to subtypes of cmis:folder as well.</li>
080 * </ul>
081 */
082public class NuxeoTypeHelper {
083
084    private static final Log log = LogFactory.getLog(NuxeoTypeHelper.class);
085
086    public static final String NUXEO_DOCUMENT = "Document";
087
088    public static final String NUXEO_FOLDER = "Folder";
089
090    public static final String NUXEO_RELATION = "Relation";
091
092    public static final String NUXEO_RELATION_DEFAULT = "DefaultRelation";
093
094    public static final String NUXEO_FILE = "File";
095
096    public static final String NUXEO_ORDERED_FOLDER = "OrderedFolder";
097
098    public static final String FACET_TYPE_PREFIX = "facet:";
099
100    public static final String NX_DUBLINCORE = "dublincore";
101
102    public static final String NX_DC_TITLE = "dc:title";
103
104    public static final String NX_DC_DESCRIPTION = "dc:description";
105
106    public static final String NX_DC_CREATED = "dc:created";
107
108    public static final String NX_DC_CREATOR = "dc:creator";
109
110    public static final String NX_DC_MODIFIED = "dc:modified";
111
112    public static final String NX_DC_LAST_CONTRIBUTOR = "dc:lastContributor";
113
114    public static final String NX_ICON = "common:icon";
115
116    public static final String NX_REL_SOURCE = "relation:source";
117
118    public static final String NX_REL_TARGET = "relation:target";
119
120    public static final String NX_DIGEST = "nuxeo:contentStreamDigest";
121
122    public static final String NX_ISVERSION = "nuxeo:isVersion";
123
124    public static final String NX_ISCHECKEDIN = "nuxeo:isCheckedIn";
125
126    /**
127     * @since 10.2
128     */
129    public static final String NX_ISTRASHED = "nuxeo:isTrashed";
130
131    public static final String NX_FACETS = "nuxeo:secondaryObjectTypeIds";
132
133    public static final String NX_LIFECYCLE_STATE = "nuxeo:lifecycleState";
134
135    public static final String NX_PARENT_ID = "nuxeo:parentId";
136
137    public static final String NX_PATH_SEGMENT = "nuxeo:pathSegment";
138
139    public static final String ENABLE_COMPLEX_PROPERTIES = "org.nuxeo.cmis.enableComplexProperties";
140
141    /** @since 6.0 */
142    public static final String NX_POS = "nuxeo:pos";
143
144    private static final String NAMESPACE = "http://ns.nuxeo.org/cmis/type/";
145
146    private static final String NAMESPACE_FACET = "http://ns.nuxeo.org/cmis/facet/";
147
148    protected static boolean isComplexPropertiesEnabled() {
149        return !Framework.isBooleanPropertyFalse(ENABLE_COMPLEX_PROPERTIES);
150    }
151
152    protected AbstractTypeDefinition t;
153
154    // used to track down and log duplicates
155    protected Map<String, String> propertyToSchema;
156
157    protected CmisVersion cmisVersion;
158
159    /**
160     * Helper to construct one CMIS type from a {@link DocumentType}.
161     */
162    protected NuxeoTypeHelper(String id, String parentId, BaseTypeId baseTypeId, DocumentType documentType,
163            String nuxeoTypeId, boolean creatable, CmisVersion cmisVersion) {
164        propertyToSchema = new HashMap<>();
165        this.cmisVersion = cmisVersion;
166        constructBaseDocumentType(id, parentId, baseTypeId, documentType, nuxeoTypeId, creatable);
167    }
168
169    /**
170     * Helper to construct one CMIS type from a secondary type.
171     */
172    protected NuxeoTypeHelper(String id, String nuxeoTypeId, CmisVersion cmisVersion) {
173        propertyToSchema = new HashMap<>();
174        this.cmisVersion = cmisVersion;
175        constructBaseSecondaryType(id, nuxeoTypeId);
176    }
177
178    /**
179     * Gets the remapped parent type id, or {@code null} if the type is to be ignored.
180     */
181    public static String getParentTypeId(DocumentType documentType) {
182        if (!documentType.hasSchema(NX_DUBLINCORE)) {
183            // ignore type without dublincore
184            return null;
185        }
186        if (documentType.getFacets().contains(FacetNames.HIDDEN_IN_NAVIGATION)) {
187            // ignore hiddeninnavigation type except if it's a relation
188            if (getBaseTypeId(documentType) != BaseTypeId.CMIS_RELATIONSHIP) {
189                return null;
190            }
191        }
192        String nuxeoTypeId = documentType.getName();
193        // NUXEO_DOCUMENT already excluded be previous checks
194        if (NUXEO_FOLDER.equals(nuxeoTypeId)) {
195            // Folder has artificial parent cmis:folder
196            return BaseTypeId.CMIS_FOLDER.value();
197        }
198        if (NUXEO_RELATION.equals(nuxeoTypeId)) {
199            // Relation has artificial parent cmis:relationship
200            return BaseTypeId.CMIS_RELATIONSHIP.value();
201        }
202        DocumentType superType = (DocumentType) documentType.getSuperType();
203        if (superType == null) {
204            return null;
205        }
206        String parentId = mappedId(superType.getName());
207        if (NUXEO_FOLDER.equals(parentId)) {
208            // reparent Folder child under cmis:folder
209            parentId = BaseTypeId.CMIS_FOLDER.value();
210        }
211        if (NUXEO_RELATION.equals(parentId)) {
212            // reparent Relation child under cmis:relationship
213            parentId = BaseTypeId.CMIS_RELATIONSHIP.value();
214        }
215        if (NUXEO_DOCUMENT.equals(parentId)) {
216            // reparent Document child under cmis:document
217            parentId = BaseTypeId.CMIS_DOCUMENT.value();
218        }
219        return parentId;
220    }
221
222    public static TypeDefinition constructDocumentType(DocumentType documentType, String parentId,
223            CmisVersion cmisVersion) {
224        String nuxeoTypeId = documentType.getName();
225        String id = mappedId(nuxeoTypeId);
226        NuxeoTypeHelper h = new NuxeoTypeHelper(id, parentId, getBaseTypeId(documentType), documentType, nuxeoTypeId,
227                true, cmisVersion);
228        // Nuxeo Property Definitions
229        for (Schema schema : documentType.getSchemas()) {
230            h.addSchemaPropertyDefinitions(schema);
231        }
232        return h.t;
233    }
234
235    public static TypeDefinition constructSecondaryType(CompositeType type, CmisVersion cmisVersion) {
236        String nuxeoTypeId = type.getName();
237        String id = FACET_TYPE_PREFIX + nuxeoTypeId;
238        NuxeoTypeHelper h = new NuxeoTypeHelper(id, nuxeoTypeId, cmisVersion);
239        // Nuxeo Property Definitions
240        for (Schema schema : type.getSchemas()) {
241            h.addSchemaPropertyDefinitions(schema);
242        }
243        return h.t;
244    }
245
246    /**
247     * Constructs a base type, not mapped to a Nuxeo type. If not a secondary, it has the dublincore schema.
248     */
249    public static TypeDefinition constructCmisBase(BaseTypeId baseTypeId, SchemaManager schemaManager,
250            CmisVersion cmisVersion) {
251        NuxeoTypeHelper h;
252        if (baseTypeId != BaseTypeId.CMIS_SECONDARY) {
253            h = new NuxeoTypeHelper(baseTypeId.value(), null, baseTypeId, null, null, true, cmisVersion);
254            DocumentType dt = schemaManager.getDocumentType(NUXEO_FOLDER); // has dc
255            h.addSchemaPropertyDefinitions(dt.getSchema(NX_DUBLINCORE));
256        } else {
257            h = new NuxeoTypeHelper(baseTypeId.value(), null, cmisVersion);
258        }
259        return h.t;
260    }
261
262    protected void addSchemaPropertyDefinitions(Schema schema) {
263        for (Field field : schema.getFields()) {
264            PropertyType propertyType;
265            Cardinality cardinality;
266            Type fieldType = field.getType();
267            String schemaName = schema.getName();
268            boolean queryable;
269            boolean orderable;
270            if (fieldType.isComplexType()) {
271                if (isComplexPropertiesEnabled()) {
272                    // content is specifically excluded from the properties
273                    if ("content".equals(fieldType.getName())) {
274                        log.debug("Ignoring complex type: " + schemaName + '/' + field.getName() + " in type: "
275                                + t.getId());
276                        continue;
277                    }
278                    // complex types get exposed to CMIS as a single string value;
279                    // the NuxeoPropertyData class will marshal/unmarshal them as JSON.
280                    cardinality = Cardinality.SINGLE;
281                    propertyType = PropertyType.STRING;
282                    queryable = false;
283                    orderable = false;
284                } else {
285                    log.debug("Ignoring complex type: " + schemaName + '/' + field.getName() + " in type: " + t.getId());
286                    continue;
287                }
288            } else {
289                if (fieldType.isListType()) {
290                    Type listFieldType = ((ListType) fieldType).getFieldType();
291                    if (!listFieldType.isSimpleType()) {
292                        if (isComplexPropertiesEnabled()) {
293                            // complex lists get exposed to CMIS as a list of string values;
294                            // the NuxeoPropertyData class will marshal/unmarshal them as JSON.
295                            cardinality = Cardinality.MULTI;
296                            propertyType = PropertyType.STRING;
297                            queryable = false;
298                            orderable = false;
299                        } else {
300                            log.debug("Ignoring complex type: " + schemaName + '/' + field.getName() + "in type: "
301                                    + t.getId());
302                            continue;
303                        }
304                    } else {
305                        // array: use a collection table
306                        cardinality = Cardinality.MULTI;
307                        propertyType = getPropertType((SimpleType) listFieldType);
308                        queryable = false;
309                        orderable = false;
310                    }
311                } else {
312                    // primitive type
313                    cardinality = Cardinality.SINGLE;
314                    propertyType = getPropertType((SimpleType) fieldType);
315                    queryable = true;
316                    orderable = true;
317                }
318            }
319            String name = field.getName().getPrefixedName();
320            PropertyDefinition<?> pd = newPropertyDefinition(name, name, propertyType, cardinality,
321                    Updatability.READWRITE, false, false, queryable, orderable);
322            if (t.getPropertyDefinitions().containsKey(pd.getId())) {
323                log.error("Type '" + t.getId() + "' has duplicate property '" + name + "' in schemas '"
324                        + propertyToSchema.get(pd.getId()) + "' and '" + schemaName + "', ignoring the one in '"
325                        + schemaName + "'");
326                continue;
327            }
328            propertyToSchema.put(pd.getId(), schemaName);
329            t.addPropertyDefinition(pd);
330        }
331    }
332
333    /**
334     * Constructs the base for a {@link DocumentType}.
335     */
336    protected void constructBaseDocumentType(String id, String parentId, BaseTypeId baseTypeId, DocumentType documentType,
337            String nuxeoTypeId, boolean creatable) {
338        if (baseTypeId == BaseTypeId.CMIS_FOLDER) {
339            t = new FolderTypeDefinitionImpl();
340        } else if (baseTypeId == BaseTypeId.CMIS_RELATIONSHIP) {
341            t = new RelationshipTypeDefinitionImpl();
342        } else {
343            t = new DocumentTypeDefinitionImpl();
344        }
345        t.setBaseTypeId(baseTypeId);
346        t.setId(id);
347        t.setParentTypeId(parentId);
348        t.setDescription(id);
349        t.setDisplayName(id);
350        t.setLocalName(nuxeoTypeId == null ? id : nuxeoTypeId);
351        Namespace ns = documentType == null ? null : documentType.getNamespace();
352        t.setLocalNamespace(ns == null ? NAMESPACE : ns.uri);
353        t.setQueryName(id);
354        t.setIsCreatable(Boolean.valueOf(creatable));
355        t.setIsQueryable(Boolean.TRUE);
356        t.setIsIncludedInSupertypeQuery(Boolean.TRUE);
357        t.setIsFulltextIndexed(Boolean.TRUE);
358        t.setIsControllableAcl(Boolean.TRUE);
359        t.setIsControllablePolicy(Boolean.FALSE);
360        addBasePropertyDefinitions();
361        if (t instanceof FolderTypeDefinitionImpl) {
362            t.setIsFileable(Boolean.TRUE);
363            FolderTypeDefinitionImpl ft = (FolderTypeDefinitionImpl) t;
364            addFolderPropertyDefinitions(ft);
365        } else if (t instanceof RelationshipTypeDefinitionImpl) {
366            RelationshipTypeDefinitionImpl rt = (RelationshipTypeDefinitionImpl) t;
367            rt.setAllowedSourceTypes(null);
368            rt.setAllowedTargetTypes(null);
369            addRelationshipPropertyDefinitions(rt);
370            t.setIsFileable(Boolean.FALSE);
371        } else {
372            DocumentTypeDefinitionImpl dt = (DocumentTypeDefinitionImpl) t;
373            boolean versionable = documentType == null ? false : documentType.getFacets().contains(
374                    FacetNames.VERSIONABLE);
375            dt.setIsVersionable(Boolean.valueOf(versionable));
376            t.setIsFileable(Boolean.TRUE);
377            ContentStreamAllowed csa = (documentType != null && supportsBlobHolder(documentType)) ? ContentStreamAllowed.ALLOWED
378                    : ContentStreamAllowed.NOTALLOWED;
379            dt.setContentStreamAllowed(csa);
380            addDocumentPropertyDefinitions(dt);
381        }
382    }
383
384    /**
385     * Constructs the base for a secondary type.
386     */
387    protected void constructBaseSecondaryType(String id, String nuxeoTypeId) {
388        t = new SecondaryTypeDefinitionImpl();
389        t.setBaseTypeId(BaseTypeId.CMIS_SECONDARY);
390        t.setId(id);
391        t.setParentTypeId(nuxeoTypeId == null ? null : BaseTypeId.CMIS_SECONDARY.value());
392        t.setDescription(id);
393        t.setDisplayName(id);
394        t.setLocalName(nuxeoTypeId == null ? id : nuxeoTypeId);
395        t.setLocalNamespace(NAMESPACE_FACET);
396        t.setQueryName(id);
397        t.setIsCreatable(Boolean.FALSE);
398        t.setIsQueryable(Boolean.TRUE);
399        t.setIsIncludedInSupertypeQuery(Boolean.TRUE);
400        t.setIsFulltextIndexed(Boolean.TRUE);
401        t.setIsControllableAcl(Boolean.FALSE);
402        t.setIsControllablePolicy(Boolean.FALSE);
403        t.setIsFileable(Boolean.FALSE);
404    }
405
406    protected void addBasePropertyDefinitions() {
407        t.addPropertyDefinition(newPropertyDefinition(PropertyIds.OBJECT_ID, "Object ID", PropertyType.ID,
408                Cardinality.SINGLE, Updatability.READONLY, false, false, true, true));
409        t.addPropertyDefinition(newPropertyDefinition(PropertyIds.OBJECT_TYPE_ID, "Type ID", PropertyType.ID,
410                Cardinality.SINGLE, Updatability.ONCREATE, false, true, false, false));
411        t.addPropertyDefinition(newPropertyDefinition(PropertyIds.BASE_TYPE_ID, "Base Type ID", PropertyType.ID,
412                Cardinality.SINGLE, Updatability.READONLY, false, false, false, false));
413        if (cmisVersion != CmisVersion.CMIS_1_0) {
414            t.addPropertyDefinition(newPropertyDefinition(PropertyIds.SECONDARY_OBJECT_TYPE_IDS, "Secondary Type IDs",
415                    PropertyType.ID, Cardinality.MULTI, Updatability.READONLY, false, false, false, false));
416        }
417        t.addPropertyDefinition(newPropertyDefinition(PropertyIds.NAME, "Name", PropertyType.STRING,
418                Cardinality.SINGLE, Updatability.READWRITE, false, true, true, true));
419        if (cmisVersion != CmisVersion.CMIS_1_0) {
420            t.addPropertyDefinition(newPropertyDefinition(PropertyIds.DESCRIPTION, "Description", PropertyType.STRING,
421                    Cardinality.SINGLE, Updatability.READWRITE, false, false, true, true));
422        }
423        t.addPropertyDefinition(newPropertyDefinition(PropertyIds.CREATED_BY, "Created By", PropertyType.STRING,
424                Cardinality.SINGLE, Updatability.READONLY, false, false, true, true));
425        t.addPropertyDefinition(newPropertyDefinition(PropertyIds.CREATION_DATE, "Creation Date",
426                PropertyType.DATETIME, Cardinality.SINGLE, Updatability.READONLY, false, false, true, true));
427        t.addPropertyDefinition(newPropertyDefinition(PropertyIds.LAST_MODIFIED_BY, "Last Modified By",
428                PropertyType.STRING, Cardinality.SINGLE, Updatability.READONLY, false, false, true, true));
429        t.addPropertyDefinition(newPropertyDefinition(PropertyIds.LAST_MODIFICATION_DATE, "Last Modification Date",
430                PropertyType.DATETIME, Cardinality.SINGLE, Updatability.READONLY, false, false, true, true));
431        t.addPropertyDefinition(newPropertyDefinition(PropertyIds.CHANGE_TOKEN, "Change Token", PropertyType.STRING,
432                Cardinality.SINGLE, Updatability.READONLY, false, false, false, false));
433
434        // Nuxeo system properties
435        t.addPropertyDefinition(newPropertyDefinition(NX_LIFECYCLE_STATE, "Lifecycle State", PropertyType.STRING,
436                Cardinality.SINGLE, Updatability.READONLY, false, false, true, true));
437        t.addPropertyDefinition(newPropertyDefinition(NX_FACETS, "Facets", PropertyType.ID, Cardinality.MULTI,
438                Updatability.READONLY, false, false, true, false));
439        t.addPropertyDefinition(newPropertyDefinition(NX_PARENT_ID, "Nuxeo Parent ID", PropertyType.ID,
440                Cardinality.SINGLE, Updatability.READONLY, false, false, true, true));
441        t.addPropertyDefinition(newPropertyDefinition(NX_PATH_SEGMENT, "Path Segment", PropertyType.STRING,
442                Cardinality.SINGLE, Updatability.READONLY, false, false, true, true));
443        t.addPropertyDefinition(newPropertyDefinition(NX_POS, "Position", PropertyType.INTEGER, Cardinality.SINGLE,
444                Updatability.READONLY, false, false, true, true));
445    }
446
447    protected static void addFolderPropertyDefinitions(FolderTypeDefinitionImpl t) {
448        t.addPropertyDefinition(newPropertyDefinition(PropertyIds.PARENT_ID, "Parent ID", PropertyType.ID,
449                Cardinality.SINGLE, Updatability.READONLY, false, false, true, true));
450        t.addPropertyDefinition(newPropertyDefinition(PropertyIds.PATH, "Path", PropertyType.STRING,
451                Cardinality.SINGLE, Updatability.READONLY, false, false, false, false));
452        t.addPropertyDefinition(newPropertyDefinition(PropertyIds.ALLOWED_CHILD_OBJECT_TYPE_IDS,
453                "Allowed Child Object Type IDs", PropertyType.ID, Cardinality.MULTI, Updatability.READONLY, false,
454                false, false, false));
455    }
456
457    protected static void addRelationshipPropertyDefinitions(RelationshipTypeDefinitionImpl t) {
458        t.addPropertyDefinition(newPropertyDefinition(PropertyIds.SOURCE_ID, "Source ID", PropertyType.ID,
459                Cardinality.SINGLE, Updatability.READWRITE, false, true, true, true));
460        t.addPropertyDefinition(newPropertyDefinition(PropertyIds.TARGET_ID, "Target ID", PropertyType.ID,
461                Cardinality.SINGLE, Updatability.READWRITE, false, true, true, true));
462    }
463
464    protected void addDocumentPropertyDefinitions(DocumentTypeDefinitionImpl t) {
465        t.addPropertyDefinition(newPropertyDefinition(PropertyIds.IS_IMMUTABLE, "Is Immutable", PropertyType.BOOLEAN,
466                Cardinality.SINGLE, Updatability.READONLY, false, false, false, false));
467        t.addPropertyDefinition(newPropertyDefinition(PropertyIds.IS_LATEST_VERSION, "Is Latest Version",
468                PropertyType.BOOLEAN, Cardinality.SINGLE, Updatability.READONLY, false, false, true, true));
469        t.addPropertyDefinition(newPropertyDefinition(PropertyIds.IS_MAJOR_VERSION, "Is Major Version",
470                PropertyType.BOOLEAN, Cardinality.SINGLE, Updatability.READONLY, false, false, false, false));
471        t.addPropertyDefinition(newPropertyDefinition(PropertyIds.IS_LATEST_MAJOR_VERSION, "Is Latest Major Version",
472                PropertyType.BOOLEAN, Cardinality.SINGLE, Updatability.READONLY, false, false, true, true));
473        t.addPropertyDefinition(newPropertyDefinition(PropertyIds.VERSION_LABEL, "Version Label", PropertyType.STRING,
474                Cardinality.SINGLE, Updatability.READONLY, false, false, true, true));
475        t.addPropertyDefinition(newPropertyDefinition(PropertyIds.VERSION_SERIES_ID, "Version Series ID",
476                PropertyType.ID, Cardinality.SINGLE, Updatability.READONLY, false, false, false, false));
477        t.addPropertyDefinition(newPropertyDefinition(PropertyIds.IS_VERSION_SERIES_CHECKED_OUT,
478                "Is Version Series Checked Out", PropertyType.BOOLEAN, Cardinality.SINGLE, Updatability.READONLY,
479                false, false, false, false));
480        t.addPropertyDefinition(newPropertyDefinition(PropertyIds.VERSION_SERIES_CHECKED_OUT_BY,
481                "Version Series Checked Out By", PropertyType.STRING, Cardinality.SINGLE, Updatability.READONLY, false,
482                false, false, false));
483        t.addPropertyDefinition(newPropertyDefinition(PropertyIds.VERSION_SERIES_CHECKED_OUT_ID,
484                "Version Series Checked Out ID", PropertyType.ID, Cardinality.SINGLE, Updatability.READONLY, false,
485                false, false, false));
486        t.addPropertyDefinition(newPropertyDefinition(PropertyIds.CHECKIN_COMMENT, "Checkin Comment",
487                PropertyType.STRING, Cardinality.SINGLE, Updatability.READONLY, false, false, false, false));
488        // mandatory properties even when content stream not allowed
489        t.addPropertyDefinition(newPropertyDefinition(PropertyIds.CONTENT_STREAM_LENGTH, "Content Stream Length",
490                PropertyType.INTEGER, Cardinality.SINGLE, Updatability.READONLY, false, false, false, false));
491        t.addPropertyDefinition(newPropertyDefinition(PropertyIds.CONTENT_STREAM_MIME_TYPE, "MIME Type",
492                PropertyType.STRING, Cardinality.SINGLE, Updatability.READONLY, false, false, false, false));
493        t.addPropertyDefinition(newPropertyDefinition(PropertyIds.CONTENT_STREAM_FILE_NAME, "Filename",
494                PropertyType.STRING, Cardinality.SINGLE, Updatability.READONLY, false, false, false, false));
495        t.addPropertyDefinition(newPropertyDefinition(PropertyIds.CONTENT_STREAM_ID, "Content Stream ID",
496                PropertyType.ID, Cardinality.SINGLE, Updatability.READONLY, false, false, false, false));
497        t.addPropertyDefinition(newPropertyDefinition(PropertyIds.CONTENT_STREAM_HASH, "Content Stream Hashes",
498                PropertyType.STRING, Cardinality.MULTI, Updatability.READONLY, false, false, false, false));
499        // Nuxeo system properties
500        // TODO: make digest queryable at some point
501        t.addPropertyDefinition(newPropertyDefinition(NX_DIGEST, "Content Stream Digest", PropertyType.STRING,
502                Cardinality.SINGLE, Updatability.READONLY, false, false, false, false));
503        t.addPropertyDefinition(newPropertyDefinition(NX_ISVERSION, "Is Version", PropertyType.BOOLEAN,
504                Cardinality.SINGLE, Updatability.READONLY, false, false, true, true));
505        t.addPropertyDefinition(newPropertyDefinition(NX_ISCHECKEDIN, "Is Checked In PWC", PropertyType.BOOLEAN,
506                Cardinality.SINGLE, Updatability.READONLY, false, false, true, true));
507        t.addPropertyDefinition(newPropertyDefinition(NX_ISTRASHED, "Is Trashed", PropertyType.BOOLEAN,
508                Cardinality.SINGLE, Updatability.READONLY, false, false, true, true));
509        if (cmisVersion != CmisVersion.CMIS_1_0) {
510            t.addPropertyDefinition(newPropertyDefinition(PropertyIds.IS_PRIVATE_WORKING_COPY, "Is PWC",
511                    PropertyType.BOOLEAN, Cardinality.SINGLE, Updatability.READONLY, false, false, false, false));
512        }
513    }
514
515    protected static PropertyDefinition<?> newPropertyDefinition(String id, String displayName,
516            PropertyType propertyType, Cardinality cardinality, Updatability updatability, boolean inherited,
517            boolean required, boolean queryable, boolean orderable) {
518        AbstractPropertyDefinition<?> p;
519        switch (propertyType) {
520        case BOOLEAN:
521            p = new PropertyBooleanDefinitionImpl();
522            break;
523        case DATETIME:
524            p = new PropertyDateTimeDefinitionImpl();
525            break;
526        case DECIMAL:
527            p = new PropertyDecimalDefinitionImpl();
528            break;
529        case HTML:
530            p = new PropertyHtmlDefinitionImpl();
531            break;
532        case ID:
533            p = new PropertyIdDefinitionImpl();
534            break;
535        case INTEGER:
536            p = new PropertyIntegerDefinitionImpl();
537            break;
538        case STRING:
539            p = new PropertyStringDefinitionImpl();
540            break;
541        case URI:
542            p = new PropertyUriDefinitionImpl();
543            break;
544        default:
545            throw new RuntimeException(propertyType.toString());
546        }
547        p.setId(id);
548        p.setDescription(displayName);
549        p.setDisplayName(displayName);
550        p.setLocalName(id);
551        p.setLocalNamespace(null); // TODO
552        p.setQueryName(id);
553        p.setPropertyType(propertyType);
554        p.setCardinality(cardinality);
555        p.setUpdatability(updatability);
556        p.setIsInherited(Boolean.valueOf(inherited));
557        p.setIsRequired(Boolean.valueOf(required));
558        p.setIsQueryable(Boolean.valueOf(queryable));
559        p.setIsOrderable(Boolean.valueOf(orderable));
560        return p;
561    }
562
563    // TODO update BlobHolderAdapterService to be able to do this
564    // without constructing a fake document
565    protected static boolean supportsBlobHolder(DocumentType documentType) {
566        DocumentModel doc = new DocumentModelImpl(documentType.getName(), null, new Path("/"), null, null,
567                documentType.getSchemaNames(), documentType.getFacets(), null, false, null, "default", null);
568        return doc.getAdapter(BlobHolder.class) != null;
569    }
570
571    /**
572     * Turns a Nuxeo type into a CMIS type.
573     */
574    protected static String mappedId(String id) {
575        // we don't map any Nuxeo type anymore to cmis:document or cmis:folder
576        return id;
577    }
578
579    protected static PropertyType getPropertType(SimpleType type) {
580        SimpleType primitive = type.getPrimitiveType();
581        if (primitive == StringType.INSTANCE) {
582            return PropertyType.STRING;
583        } else if (primitive == BooleanType.INSTANCE) {
584            return PropertyType.BOOLEAN;
585        } else if (primitive == DateType.INSTANCE) {
586            return PropertyType.DATETIME;
587        } else if (primitive == LongType.INSTANCE) {
588            return PropertyType.INTEGER;
589        } else if (primitive == DoubleType.INSTANCE) {
590            return PropertyType.DECIMAL;
591        } else {
592            return PropertyType.STRING;
593        }
594    }
595
596    public static BaseTypeId getBaseTypeId(DocumentType type) {
597        if (type.isFolder()) {
598            return BaseTypeId.CMIS_FOLDER;
599        }
600        DocumentType t = type;
601        do {
602            if (NUXEO_RELATION.equals(t.getName())) {
603                return BaseTypeId.CMIS_RELATIONSHIP;
604            }
605            t = (DocumentType) t.getSuperType();
606        } while (t != null);
607        return BaseTypeId.CMIS_DOCUMENT;
608    }
609
610    public static BaseTypeId getBaseTypeId(DocumentModel doc) {
611        return getBaseTypeId(doc.getDocumentType());
612    }
613
614}