/*
* Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*/
package org.eclipse.ecr.opencmis.impl.server;
import java.util.HashMap;
import java.util.Map;
import org.apache.chemistry.opencmis.commons.PropertyIds;
import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition;
import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
import org.apache.chemistry.opencmis.commons.enums.Cardinality;
import org.apache.chemistry.opencmis.commons.enums.ContentStreamAllowed;
import org.apache.chemistry.opencmis.commons.enums.PropertyType;
import org.apache.chemistry.opencmis.commons.enums.Updatability;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AbstractPropertyDefinition;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AbstractTypeDefinition;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.DocumentTypeDefinitionImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.FolderTypeDefinitionImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyBooleanDefinitionImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyDateTimeDefinitionImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyDecimalDefinitionImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyHtmlDefinitionImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIdDefinitionImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIntegerDefinitionImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyStringDefinitionImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyUriDefinitionImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.RelationshipTypeDefinitionImpl;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.ecr.core.api.DocumentModel;
import org.eclipse.ecr.core.api.blobholder.BlobHolder;
import org.eclipse.ecr.core.api.impl.DocumentModelImpl;
import org.eclipse.ecr.core.schema.DocumentType;
import org.eclipse.ecr.core.schema.FacetNames;
import org.eclipse.ecr.core.schema.Namespace;
import org.eclipse.ecr.core.schema.SchemaManager;
import org.eclipse.ecr.core.schema.types.Field;
import org.eclipse.ecr.core.schema.types.ListType;
import org.eclipse.ecr.core.schema.types.Schema;
import org.eclipse.ecr.core.schema.types.SimpleType;
import org.eclipse.ecr.core.schema.types.Type;
import org.eclipse.ecr.core.schema.types.primitives.BooleanType;
import org.eclipse.ecr.core.schema.types.primitives.DateType;
import org.eclipse.ecr.core.schema.types.primitives.DoubleType;
import org.eclipse.ecr.core.schema.types.primitives.LongType;
import org.eclipse.ecr.core.schema.types.primitives.StringType;
import org.nuxeo.common.utils.Path;
/**
* Nuxeo Type Utilities.
* <p>
* Maps Nuxeo types to CMIS types using the following rules:
* <ul>
* <li>Only types containing dublincore are exposed,</li>
* <li>cmis:document and cmis:folder expose dublincore, and are not creatable,</li>
* <li>The Document type is not exposed,</li>
* <li>Types inheriting from Document are exposed as inheriting cmis:document,</li>
* <li>The Folder type is mapped to a concrete subtype of cmis:folder,</li>
* <li>Other folderish types directly under Folder are mapped to subtypes of
* cmis:folder as well.</li>
* </ul>
*/
public class NuxeoTypeHelper {
private static final Log log = LogFactory.getLog(NuxeoTypeHelper.class);
public static final String NUXEO_DOCUMENT = "Document";
public static final String NUXEO_FOLDER = "Folder";
public static final String NUXEO_RELATION = "Relation";
public static final String NUXEO_FILE = "File";
public static final String NUXEO_ORDERED_FOLDER = "OrderedFolder";
public static final String NX_DUBLINCORE = "dublincore";
public static final String NX_DC_TITLE = "dc:title";
public static final String NX_DC_CREATED = "dc:created";
public static final String NX_DC_CREATOR = "dc:creator";
public static final String NX_DC_MODIFIED = "dc:modified";
public static final String NX_DC_LAST_CONTRIBUTOR = "dc:lastContributor";
public static final String NX_ICON = "common:icon";
public static final String NX_REL_SOURCE = "relation:source";
public static final String NX_REL_TARGET = "relation:target";
private static final String NAMESPACE = "http://ns.nuxeo.org/cmis/type/";
protected AbstractTypeDefinition t;
// used to track down and log duplicates
protected Map<String, String> propertyToSchema;
/**
* Helper to construct one type.
*/
protected NuxeoTypeHelper(String id, String parentId,
BaseTypeId baseTypeId, DocumentType documentType,
String nuxeoTypeId, boolean creatable) {
propertyToSchema = new HashMap<String, String>();
constructBase(id, parentId, baseTypeId, documentType, nuxeoTypeId,
creatable);
}
/**
* Gets the remapped parent type id, or {@code null} if the type is to be
* ignored.
*/
public static String getParentTypeId(DocumentType documentType) {
if (documentType.getFacets().contains(FacetNames.HIDDEN_IN_NAVIGATION)
|| !documentType.hasSchema(NX_DUBLINCORE)) {
// ignore type
return null;
}
String nuxeoTypeId = documentType.getName();
// NUXEO_DOCUMENT already excluded be previous checks
if (NUXEO_FOLDER.equals(nuxeoTypeId)) {
// Folder has artificial parent cmis:folder
return BaseTypeId.CMIS_FOLDER.value();
}
if (NUXEO_RELATION.equals(nuxeoTypeId)) {
// Relation has artificial parent cmis:relationship
return BaseTypeId.CMIS_RELATIONSHIP.value();
}
DocumentType superType = (DocumentType) documentType.getSuperType();
if (superType == null) {
return null;
}
String parentId = mappedId(superType.getName());
if (NUXEO_FOLDER.equals(parentId)) {
// reparent Folder child under cmis:folder
parentId = BaseTypeId.CMIS_FOLDER.value();
}
if (NUXEO_RELATION.equals(parentId)) {
// reparent Relation child under cmis:relationship
parentId = BaseTypeId.CMIS_RELATIONSHIP.value();
}
if (NUXEO_DOCUMENT.equals(parentId)) {
// reparent Document child under cmis:document
parentId = BaseTypeId.CMIS_DOCUMENT.value();
}
return parentId;
}
public static TypeDefinition construct(DocumentType documentType,
String parentId) {
String nuxeoTypeId = documentType.getName();
String id = mappedId(nuxeoTypeId);
NuxeoTypeHelper h = new NuxeoTypeHelper(id, parentId,
getBaseTypeId(documentType), documentType, nuxeoTypeId, true);
// Nuxeo Property Definitions
for (Schema schema : documentType.getSchemas()) {
h.addSchemaPropertyDefinitions(schema);
}
return h.t;
}
/**
* Constructs a base type, not mapped to a Nuxeo type. It has the dublincore
* schema though. When created, it actually constructs a File or a Folder.
*/
public static TypeDefinition constructCmisBase(BaseTypeId baseTypeId,
SchemaManager schemaManager) {
NuxeoTypeHelper h = new NuxeoTypeHelper(baseTypeId.value(), null,
baseTypeId, null, null, true);
DocumentType dt = schemaManager.getDocumentType(NUXEO_FOLDER); // has dc
h.addSchemaPropertyDefinitions(dt.getSchema(NX_DUBLINCORE));
return h.t;
}
protected void addSchemaPropertyDefinitions(Schema schema) {
for (Field field : schema.getFields()) {
PropertyType propertyType;
Cardinality cardinality;
Type fieldType = field.getType();
String schemaName = schema.getName();
if (fieldType.isComplexType()) {
// complex type
log.debug("Ignoring complex type: " + schemaName + '/'
+ field.getName() + " in type: " + t.getId());
continue;
} else {
if (fieldType.isListType()) {
Type listFieldType = ((ListType) fieldType).getFieldType();
if (!listFieldType.isSimpleType()) {
// complex list
log.debug("Ignoring complex list: " + schemaName + '/'
+ field.getName() + " in type: " + t.getId());
continue;
} else {
// array: use a collection table
cardinality = Cardinality.MULTI;
propertyType = getPropertType((SimpleType) listFieldType);
}
} else {
// primitive type
cardinality = Cardinality.SINGLE;
propertyType = getPropertType((SimpleType) fieldType);
}
}
String name = field.getName().getPrefixedName();
PropertyDefinition<?> pd = newPropertyDefinition(name, name,
propertyType, cardinality, Updatability.READWRITE, false,
false, true);
if (t.getPropertyDefinitions().containsKey(pd.getId())) {
log.error("Type '" + t.getId() + "' has duplicate property '"
+ name + "' in schemas '"
+ propertyToSchema.get(pd.getId()) + "' and '"
+ schemaName + "', ignoring the one in '" + schemaName
+ "'");
continue;
}
propertyToSchema.put(pd.getId(), schemaName);
t.addPropertyDefinition(pd);
}
}
protected void constructBase(String id, String parentId,
BaseTypeId baseTypeId, DocumentType documentType,
String nuxeoTypeId, boolean creatable) {
if (baseTypeId == BaseTypeId.CMIS_FOLDER) {
t = new FolderTypeDefinitionImpl();
} else if (baseTypeId == BaseTypeId.CMIS_RELATIONSHIP) {
t = new RelationshipTypeDefinitionImpl();
} else {
t = new DocumentTypeDefinitionImpl();
}
t.setBaseTypeId(baseTypeId);
t.setId(id);
t.setParentTypeId(parentId);
t.setDescription(id);
t.setDisplayName(id);
t.setLocalName(nuxeoTypeId == null ? id : nuxeoTypeId);
Namespace ns = documentType == null ? null
: documentType.getNamespace();
t.setLocalNamespace(ns == null ? NAMESPACE : ns.uri);
t.setQueryName(id);
t.setIsCreatable(Boolean.valueOf(creatable));
t.setIsFileable(Boolean.TRUE);
t.setIsQueryable(Boolean.TRUE);
t.setIsIncludedInSupertypeQuery(Boolean.TRUE);
t.setIsFulltextIndexed(Boolean.TRUE);
t.setIsControllableAcl(Boolean.FALSE);
t.setIsControllablePolicy(Boolean.FALSE);
addBasePropertyDefinitions();
if (t instanceof FolderTypeDefinitionImpl) {
FolderTypeDefinitionImpl ft = (FolderTypeDefinitionImpl) t;
addFolderPropertyDefinitions(ft);
} else if (t instanceof RelationshipTypeDefinitionImpl) {
RelationshipTypeDefinitionImpl rt = (RelationshipTypeDefinitionImpl) t;
rt.setAllowedSourceTypes(null);
rt.setAllowedTargetTypes(null);
addRelationshipPropertyDefinitions(rt);
} else {
DocumentTypeDefinitionImpl dt = (DocumentTypeDefinitionImpl) t;
dt.setIsVersionable(Boolean.FALSE);
ContentStreamAllowed csa = (documentType != null && supportsBlobHolder(documentType)) ? ContentStreamAllowed.ALLOWED
: ContentStreamAllowed.NOTALLOWED;
dt.setContentStreamAllowed(csa);
addDocumentPropertyDefinitions(dt);
}
}
protected void addBasePropertyDefinitions() {
t.addPropertyDefinition(newPropertyDefinition(PropertyIds.OBJECT_ID,
"Object ID", PropertyType.ID, Cardinality.SINGLE,
Updatability.READONLY, false, true, true));
t.addPropertyDefinition(newPropertyDefinition(
PropertyIds.OBJECT_TYPE_ID, "Type ID", PropertyType.ID,
Cardinality.SINGLE, Updatability.ONCREATE, false, true, false));
t.addPropertyDefinition(newPropertyDefinition(PropertyIds.BASE_TYPE_ID,
"Base Type ID", PropertyType.ID, Cardinality.SINGLE,
Updatability.READONLY, false, true, false));
t.addPropertyDefinition(newPropertyDefinition(PropertyIds.NAME, "Name",
PropertyType.STRING, Cardinality.SINGLE,
Updatability.READWRITE, false, true, true));
t.addPropertyDefinition(newPropertyDefinition(PropertyIds.CREATED_BY,
"Created By", PropertyType.STRING, Cardinality.SINGLE,
Updatability.READONLY, false, true, true));
t.addPropertyDefinition(newPropertyDefinition(
PropertyIds.CREATION_DATE, "Creation Date",
PropertyType.DATETIME, Cardinality.SINGLE,
Updatability.READONLY, false, true, true));
t.addPropertyDefinition(newPropertyDefinition(
PropertyIds.LAST_MODIFIED_BY, "Last Modified By",
PropertyType.STRING, Cardinality.SINGLE, Updatability.READONLY,
false, true, false));
t.addPropertyDefinition(newPropertyDefinition(
PropertyIds.LAST_MODIFICATION_DATE, "Last Modification Date",
PropertyType.DATETIME, Cardinality.SINGLE,
Updatability.READONLY, false, true, true));
t.addPropertyDefinition(newPropertyDefinition(PropertyIds.CHANGE_TOKEN,
"Change Token", PropertyType.STRING, Cardinality.SINGLE,
Updatability.READONLY, false, false, false));
}
protected static void addFolderPropertyDefinitions(
FolderTypeDefinitionImpl t) {
t.addPropertyDefinition(newPropertyDefinition(PropertyIds.PARENT_ID,
"Parent ID", PropertyType.ID, Cardinality.SINGLE,
Updatability.READONLY, false, false, true));
t.addPropertyDefinition(newPropertyDefinition(PropertyIds.PATH, "Path",
PropertyType.STRING, Cardinality.SINGLE, Updatability.READONLY,
false, false, false));
t.addPropertyDefinition(newPropertyDefinition(
PropertyIds.ALLOWED_CHILD_OBJECT_TYPE_IDS,
"Allowed Child Object Type IDs", PropertyType.ID,
Cardinality.MULTI, Updatability.READONLY, false, false, false));
}
protected static void addRelationshipPropertyDefinitions(
RelationshipTypeDefinitionImpl t) {
t.addPropertyDefinition(newPropertyDefinition(PropertyIds.SOURCE_ID,
"Source ID", PropertyType.ID, Cardinality.SINGLE,
Updatability.READWRITE, false, false, true));
t.addPropertyDefinition(newPropertyDefinition(PropertyIds.TARGET_ID,
"Target ID", PropertyType.ID, Cardinality.SINGLE,
Updatability.READWRITE, false, false, true));
}
protected static void addDocumentPropertyDefinitions(
DocumentTypeDefinitionImpl t) {
t.addPropertyDefinition(newPropertyDefinition(PropertyIds.IS_IMMUTABLE,
"Is Immutable", PropertyType.BOOLEAN, Cardinality.SINGLE,
Updatability.READONLY, false, false, false));
t.addPropertyDefinition(newPropertyDefinition(
PropertyIds.IS_LATEST_VERSION, "Is Latest Version",
PropertyType.BOOLEAN, Cardinality.SINGLE,
Updatability.READONLY, false, false, false));
t.addPropertyDefinition(newPropertyDefinition(
PropertyIds.IS_MAJOR_VERSION, "Is Major Version",
PropertyType.BOOLEAN, Cardinality.SINGLE,
Updatability.READONLY, false, false, false));
t.addPropertyDefinition(newPropertyDefinition(
PropertyIds.IS_LATEST_MAJOR_VERSION, "Is Latest Major Version",
PropertyType.BOOLEAN, Cardinality.SINGLE,
Updatability.READONLY, false, false, false));
t.addPropertyDefinition(newPropertyDefinition(
PropertyIds.VERSION_LABEL, "Version Label",
PropertyType.STRING, Cardinality.SINGLE, Updatability.READONLY,
false, true, true));
t.addPropertyDefinition(newPropertyDefinition(
PropertyIds.VERSION_SERIES_ID, "Version Series ID",
PropertyType.ID, Cardinality.SINGLE, Updatability.READONLY,
false, true, false));
t.addPropertyDefinition(newPropertyDefinition(
PropertyIds.IS_VERSION_SERIES_CHECKED_OUT,
"Is Version Series Checked Out", PropertyType.BOOLEAN,
Cardinality.SINGLE, Updatability.READONLY, false, true, false));
t.addPropertyDefinition(newPropertyDefinition(
PropertyIds.VERSION_SERIES_CHECKED_OUT_BY,
"Version Series Checked Out By", PropertyType.STRING,
Cardinality.SINGLE, Updatability.READONLY, false, false, false));
t.addPropertyDefinition(newPropertyDefinition(
PropertyIds.VERSION_SERIES_CHECKED_OUT_ID,
"Version Series Checked Out ID", PropertyType.ID,
Cardinality.SINGLE, Updatability.READONLY, false, false, false));
t.addPropertyDefinition(newPropertyDefinition(
PropertyIds.CHECKIN_COMMENT, "Checkin Comment",
PropertyType.STRING, Cardinality.SINGLE, Updatability.READONLY,
false, false, false));
// mandatory properties even when content stream not allowed
t.addPropertyDefinition(newPropertyDefinition(
PropertyIds.CONTENT_STREAM_LENGTH, "Content Stream Length",
PropertyType.INTEGER, Cardinality.SINGLE,
Updatability.READONLY, false, false, false));
t.addPropertyDefinition(newPropertyDefinition(
PropertyIds.CONTENT_STREAM_MIME_TYPE, "MIME Type",
PropertyType.STRING, Cardinality.SINGLE, Updatability.READONLY,
false, false, false));
t.addPropertyDefinition(newPropertyDefinition(
PropertyIds.CONTENT_STREAM_FILE_NAME, "Filename",
PropertyType.STRING, Cardinality.SINGLE, Updatability.READONLY,
false, false, false));
t.addPropertyDefinition(newPropertyDefinition(
PropertyIds.CONTENT_STREAM_ID, "Content Stream ID",
PropertyType.ID, Cardinality.SINGLE, Updatability.READONLY,
false, false, false));
}
protected static PropertyDefinition<?> newPropertyDefinition(String id,
String displayName, PropertyType propertyType,
Cardinality cardinality, Updatability updatability,
boolean inherited, boolean required, boolean queryable) {
AbstractPropertyDefinition<?> p;
switch (propertyType) {
case BOOLEAN:
p = new PropertyBooleanDefinitionImpl();
break;
case DATETIME:
p = new PropertyDateTimeDefinitionImpl();
break;
case DECIMAL:
p = new PropertyDecimalDefinitionImpl();
break;
case HTML:
p = new PropertyHtmlDefinitionImpl();
break;
case ID:
p = new PropertyIdDefinitionImpl();
break;
case INTEGER:
p = new PropertyIntegerDefinitionImpl();
break;
case STRING:
p = new PropertyStringDefinitionImpl();
break;
case URI:
p = new PropertyUriDefinitionImpl();
break;
default:
throw new RuntimeException(propertyType.toString());
}
p.setId(id);
p.setDescription(displayName);
p.setDisplayName(displayName);
p.setLocalName(id);
p.setLocalNamespace(null); // TODO
p.setQueryName(id);
p.setPropertyType(propertyType);
p.setCardinality(cardinality);
p.setUpdatability(updatability);
p.setIsInherited(Boolean.valueOf(inherited));
p.setIsRequired(Boolean.valueOf(required));
p.setIsQueryable(Boolean.valueOf(queryable));
return p;
}
// TODO update BlobHolderAdapterService to be able to do this
// without constructing a fake document
protected static boolean supportsBlobHolder(DocumentType documentType) {
DocumentModel doc = new DocumentModelImpl(null, documentType.getName(),
null, new Path("/"), null, null, null,
documentType.getSchemaNames(), documentType.getFacets(), null,
"default");
return doc.getAdapter(BlobHolder.class) != null;
}
/**
* Turns a Nuxeo type into a CMIS type.
*/
protected static String mappedId(String id) {
// we don't map any Nuxeo type anymore to cmis:document or cmis:folder
return id;
}
protected static PropertyType getPropertType(SimpleType type) {
SimpleType primitive = type.getPrimitiveType();
if (primitive == StringType.INSTANCE) {
return PropertyType.STRING;
} else if (primitive == BooleanType.INSTANCE) {
return PropertyType.BOOLEAN;
} else if (primitive == DateType.INSTANCE) {
return PropertyType.DATETIME;
} else if (primitive == LongType.INSTANCE) {
return PropertyType.INTEGER;
} else if (primitive == DoubleType.INSTANCE) {
return PropertyType.DECIMAL;
} else {
return PropertyType.STRING;
}
}
public static BaseTypeId getBaseTypeId(DocumentType type) {
if (type.isFolder()) {
return BaseTypeId.CMIS_FOLDER;
}
DocumentType t = type;
do {
if (NUXEO_RELATION.equals(t.getName())) {
return BaseTypeId.CMIS_RELATIONSHIP;
}
t = (DocumentType) t.getSuperType();
} while (t != null);
return BaseTypeId.CMIS_DOCUMENT;
}
public static BaseTypeId getBaseTypeId(DocumentModel doc) {
return getBaseTypeId(doc.getDocumentType());
}
}