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