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