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