/* * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Contributors: * Florent Guillaume */ package org.nuxeo.ecm.core.storage.sql; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuxeo.ecm.core.api.NuxeoException; import org.nuxeo.ecm.core.schema.DocumentType; import org.nuxeo.ecm.core.schema.PrefetchInfo; import org.nuxeo.ecm.core.schema.SchemaManager; import org.nuxeo.ecm.core.schema.types.ComplexType; import org.nuxeo.ecm.core.schema.types.CompositeType; import org.nuxeo.ecm.core.schema.types.Field; import org.nuxeo.ecm.core.schema.types.ListType; import org.nuxeo.ecm.core.schema.types.Schema; import org.nuxeo.ecm.core.schema.types.Type; import org.nuxeo.ecm.core.schema.types.primitives.BooleanType; import org.nuxeo.ecm.core.schema.types.primitives.DateType; import org.nuxeo.ecm.core.schema.types.primitives.LongType; import org.nuxeo.ecm.core.schema.types.primitives.StringType; import org.nuxeo.ecm.core.storage.FulltextConfiguration; import org.nuxeo.ecm.core.storage.sql.RepositoryDescriptor.FieldDescriptor; import org.nuxeo.ecm.core.storage.sql.RowMapper.IdWithTypes; import org.nuxeo.ecm.core.storage.sql.jdbc.SQLInfo; import org.nuxeo.runtime.api.Framework; /** * The {@link Model} is the link between high-level types and SQL-level objects (entity tables, collections). It defines * all policies relating to the choice of structure (what schema are grouped together in for optimization) and names in * the SQL database (table names, column names), and to what entity names (type name, field name) they correspond. * <p> * A Nuxeo schema or type is mapped to a SQL-level table. Several types can be aggregated in the same table. In theory, * a type could even be split into different tables. * * @author Florent Guillaume */ public class Model { private static final Log log = LogFactory.getLog(Model.class); public static final String ROOT_TYPE = "Root"; public static final String REPOINFO_TABLE_NAME = "repositories"; public static final String REPOINFO_REPONAME_KEY = "name"; public static final String MAIN_KEY = "id"; public static final String CLUSTER_NODES_TABLE_NAME = "cluster_nodes"; public static final String CLUSTER_NODES_NODEID_KEY = "nodeid"; public static final String CLUSTER_NODES_CREATED_KEY = "created"; public static final String CLUSTER_INVALS_TABLE_NAME = "cluster_invals"; public static final String CLUSTER_INVALS_NODEID_KEY = "nodeid"; public static final String CLUSTER_INVALS_ID_KEY = "id"; public static final String CLUSTER_INVALS_FRAGMENTS_KEY = "fragments"; public static final String CLUSTER_INVALS_KIND_KEY = "kind"; public static final String MAIN_PRIMARY_TYPE_PROP = "ecm:primaryType"; public static final String MAIN_PRIMARY_TYPE_KEY = "primarytype"; public static final String MAIN_MIXIN_TYPES_PROP = "ecm:mixinTypes"; public static final String MAIN_MIXIN_TYPES_KEY = "mixintypes"; public static final String MAIN_BASE_VERSION_PROP = "ecm:baseVersion"; public static final String MAIN_BASE_VERSION_KEY = "baseversionid"; public static final String MAIN_CHECKED_IN_PROP = "ecm:isCheckedIn"; public static final String MAIN_CHECKED_IN_KEY = "ischeckedin"; public static final String MAIN_MAJOR_VERSION_PROP = "ecm:majorVersion"; public static final String MAIN_MAJOR_VERSION_KEY = "majorversion"; public static final String MAIN_MINOR_VERSION_PROP = "ecm:minorVersion"; public static final String MAIN_MINOR_VERSION_KEY = "minorversion"; public static final String MAIN_IS_VERSION_PROP = "ecm:isVersion"; public static final String MAIN_IS_VERSION_KEY = "isversion"; public static final String MAIN_CHANGE_TOKEN_PROP = "ecm:changeToken"; public static final String MAIN_CHANGE_TOKEN_KEY = "changetoken"; // for soft-delete public static final String MAIN_IS_DELETED_PROP = "ecm:isDeleted"; // for soft-delete public static final String MAIN_IS_DELETED_KEY = "isdeleted"; // for soft-delete public static final String MAIN_DELETED_TIME_PROP = "ecm:deletedTime"; // for soft-delete public static final String MAIN_DELETED_TIME_KEY = "deletedtime"; public static final String UID_SCHEMA_NAME = "uid"; public static final String UID_MAJOR_VERSION_KEY = "major_version"; public static final String UID_MINOR_VERSION_KEY = "minor_version"; public static final String HIER_TABLE_NAME = "hierarchy"; public static final String HIER_PARENT_KEY = "parentid"; public static final String HIER_CHILD_NAME_KEY = "name"; public static final String HIER_CHILD_POS_KEY = "pos"; public static final String HIER_CHILD_ISPROPERTY_KEY = "isproperty"; public static final String ANCESTORS_TABLE_NAME = "ancestors"; public static final String ANCESTORS_ANCESTOR_KEY = "ancestors"; public static final String COLL_TABLE_POS_KEY = "pos"; public static final String COLL_TABLE_VALUE_KEY = "item"; public static final String MISC_TABLE_NAME = "misc"; public static final String MISC_LIFECYCLE_POLICY_PROP = "ecm:lifeCyclePolicy"; public static final String MISC_LIFECYCLE_POLICY_KEY = "lifecyclepolicy"; public static final String MISC_LIFECYCLE_STATE_PROP = "ecm:lifeCycleState"; public static final String MISC_LIFECYCLE_STATE_KEY = "lifecyclestate"; public static final String ACL_TABLE_NAME = "acls"; public static final String ACL_PROP = "ecm:acl"; public static final String ACL_POS_KEY = "pos"; public static final String ACL_NAME_KEY = "name"; public static final String ACL_GRANT_KEY = "grant"; public static final String ACL_PERMISSION_KEY = "permission"; public static final String ACL_CREATOR_KEY = "creator"; public static final String ACL_BEGIN_KEY = "begin"; public static final String ACL_END_KEY = "end"; public static final String ACL_STATUS_KEY = "status"; public static final String ACL_USER_KEY = "user"; public static final String ACL_GROUP_KEY = "group"; public static final String VERSION_TABLE_NAME = "versions"; public static final String VERSION_VERSIONABLE_PROP = "ecm:versionableId"; public static final String VERSION_VERSIONABLE_KEY = "versionableid"; public static final String VERSION_CREATED_PROP = "ecm:versionCreated"; public static final String VERSION_CREATED_KEY = "created"; public static final String VERSION_LABEL_PROP = "ecm:versionLabel"; public static final String VERSION_LABEL_KEY = "label"; public static final String VERSION_DESCRIPTION_PROP = "ecm:versionDescription"; public static final String VERSION_DESCRIPTION_KEY = "description"; public static final String VERSION_IS_LATEST_PROP = "ecm:isLatestVersion"; public static final String VERSION_IS_LATEST_KEY = "islatest"; public static final String VERSION_IS_LATEST_MAJOR_PROP = "ecm:isLatestMajorVersion"; public static final String VERSION_IS_LATEST_MAJOR_KEY = "islatestmajor"; public static final String PROXY_TYPE = "ecm:proxy"; public static final String PROXY_TABLE_NAME = "proxies"; public static final String PROXY_TARGET_PROP = "ecm:proxyTargetId"; public static final String PROXY_TARGET_KEY = "targetid"; public static final String PROXY_VERSIONABLE_PROP = "ecm:proxyVersionableId"; public static final String PROXY_VERSIONABLE_KEY = "versionableid"; public static final String LOCK_TABLE_NAME = "locks"; public static final String LOCK_OWNER_PROP = "ecm:lockOwner"; public static final String LOCK_OWNER_KEY = "owner"; public static final String LOCK_CREATED_PROP = "ecm:lockCreated"; public static final String LOCK_CREATED_KEY = "created"; public static final String FULLTEXT_DEFAULT_INDEX = "default"; // not // config public static final String FULLTEXT_TABLE_NAME = "fulltext"; public static final String FULLTEXT_JOBID_PROP = "ecm:fulltextJobId"; public static final String FULLTEXT_JOBID_KEY = "jobid"; public static final String FULLTEXT_FULLTEXT_PROP = "ecm:fulltext"; public static final String FULLTEXT_FULLTEXT_KEY = "fulltext"; public static final String FULLTEXT_SIMPLETEXT_PROP = "ecm:simpleText"; public static final String FULLTEXT_SIMPLETEXT_KEY = "simpletext"; public static final String FULLTEXT_BINARYTEXT_PROP = "ecm:binaryText"; public static final String FULLTEXT_BINARYTEXT_KEY = "binarytext"; public static final String HIER_READ_ACL_TABLE_NAME = "hierarchy_read_acl"; public static final String HIER_READ_ACL_ID = "id"; public static final String HIER_READ_ACL_ACL_ID = "acl_id"; public static final String ACLR_USER_MAP_TABLE_NAME = "aclr_user_map"; public static final String ACLR_USER_MAP_USER_ID = "user_id"; public static final String ACLR_USER_MAP_ACL_ID = "acl_id"; /** Specified in ext. point to use CLOBs. */ public static final String FIELD_TYPE_LARGETEXT = "largetext"; /** Specified in ext. point to use array instead of collection table. */ public static final String FIELD_TYPE_ARRAY = "array"; /** Specified in ext. point to use CLOB array instead of collection table. */ public static final String FIELD_TYPE_ARRAY_LARGETEXT = "array_largetext"; // some random long that's not in the database // first half of md5 of "nosuchlongid" public static final Long NO_SUCH_LONG_ID = Long.valueOf(0x3153147dd69fcea4L); public static final String INITIAL_CHANGE_TOKEN = "0"; protected final boolean softDeleteEnabled; protected final boolean proxiesEnabled; /** Type of ids as seen by the VCS Java layer. */ public enum IdType { STRING, // LONG, // } // type of id seen by the VCS Java layer protected final IdType idType; // type for VCS row storage protected final PropertyType idPropertyType; // type for core properties protected final Type idCoreType; /** * If true, the misc columns are added to hierarchy, not to a separate misc table. */ protected final boolean miscInHierarchy; protected final RepositoryDescriptor repositoryDescriptor; /** Per-doctype list of schemas. */ private final Map<String, Set<String>> allDocTypeSchemas; /** Per-mixin list of schemas. */ private final Map<String, Set<String>> allMixinSchemas; /** The proxy schemas. */ private final Set<String> allProxySchemas; /** Map of mixin to doctypes. */ private final Map<String, Set<String>> mixinsDocumentTypes; /** Map of doctype to mixins, for search. */ protected final Map<String, Set<String>> documentTypesMixins; /** Shared high-level properties that don't come from the schema manager. */ private final Map<String, Type> specialPropertyTypes; /** Map of fragment to key to property info. */ private final Map<String, Map<String, ModelProperty>> fragmentPropertyInfos; /** Map of schema to property to property info. */ private final Map<String, Map<String, ModelProperty>> schemaPropertyInfos; /** Map of docType/complexType to property to property info. */ private final Map<String, Map<String, ModelProperty>> typePropertyInfos; /** Map of mixin to property to property info. */ private final Map<String, Map<String, ModelProperty>> mixinPropertyInfos; /** The proxy property infos. */ private final Map<String, ModelProperty> proxyPropertyInfos; /** Map of property to property info. */ private final Map<String, ModelProperty> sharedPropertyInfos; /** Merged properties (all schemas together + shared). */ private final Map<String, ModelProperty> mergedPropertyInfos; /** Per-schema map of path to property info. */ private final Map<String, Map<String, ModelProperty>> schemaPathPropertyInfos; /** Map of prefix to schema. */ private final Map<String, String> prefixToSchema; /** Per-schema set of path to simple fulltext properties. */ private final Map<String, Set<String>> schemaSimpleTextPaths; /** * Map of path (from all doc types) to property info. Value is NONE for valid complex property path prefixes. */ private final Map<String, ModelProperty> allPathPropertyInfos; /** Map of fragment to key to column type. */ private final Map<String, Map<String, ColumnType>> fragmentKeyTypes; /** Map of fragment to keys for binary columns. */ private final Map<String, List<String>> binaryFragmentKeys; /** Maps collection table names to their type. */ private final Map<String, PropertyType> collectionTables; /** Column ordering for collections. */ private final Map<String, String> collectionOrderBy; // ------------------------------------------------------- /** * Map of schema to simple+collection fragments. Used while computing document type fragments, and for prefetch. */ private final Map<String, Set<String>> schemaFragments; /** Map of doctype/complextype to simple+collection fragments. */ protected final Map<String, Set<String>> typeFragments; /** Map of mixin to simple+collection fragments. */ protected final Map<String, Set<String>> mixinFragments; /** The proxy fragments. */ private final Set<String> proxyFragments; /** Map of doctype to prefetched fragments. */ protected final Map<String, Set<String>> docTypePrefetchedFragments; /** Map of schema to child name to type. */ protected final Map<String, Map<String, String>> schemaComplexChildren; /** Map of doctype/complextype to child name to type. */ protected final Map<String, Map<String, String>> typeComplexChildren; /** Map of mixin to child name to type. */ protected final Map<String, Map<String, String>> mixinComplexChildren; /** Map of doctype to its supertype, for search. */ protected final Map<String, String> documentSuperTypes; /** Map of doctype to its subtypes (including itself), for search. */ protected final Map<String, Set<String>> documentSubTypes; /** Map of field name to fragment holding it. Used for prefetch. */ protected final Map<String, String> fieldFragment; protected final FulltextConfiguration fulltextConfiguration; protected final Set<String> noPerDocumentQueryFacets; /** * Map of fragment -> info about whether there's a fulltext text field (PropertyType.STRING), binary field * (PropertyType.BINARY), or both (PropertyType.BOOLEAN). */ protected final Map<String, PropertyType> fulltextInfoByFragment; private final boolean materializeFulltextSyntheticColumn; private final boolean supportsArrayColumns; public Model(ModelSetup modelSetup) { repositoryDescriptor = modelSetup.repositoryDescriptor; materializeFulltextSyntheticColumn = modelSetup.materializeFulltextSyntheticColumn; supportsArrayColumns = modelSetup.supportsArrayColumns; idType = modelSetup.idType; switch (idType) { case STRING: idPropertyType = PropertyType.STRING; idCoreType = StringType.INSTANCE; break; case LONG: idPropertyType = PropertyType.LONG; idCoreType = LongType.INSTANCE; break; default: throw new AssertionError(idType.toString()); } softDeleteEnabled = repositoryDescriptor.getSoftDeleteEnabled(); proxiesEnabled = repositoryDescriptor.getProxiesEnabled(); allDocTypeSchemas = new HashMap<String, Set<String>>(); mixinsDocumentTypes = new HashMap<String, Set<String>>(); documentTypesMixins = new HashMap<String, Set<String>>(); allMixinSchemas = new HashMap<String, Set<String>>(); allProxySchemas = new HashSet<String>(); fragmentPropertyInfos = new HashMap<String, Map<String, ModelProperty>>(); schemaPropertyInfos = new HashMap<String, Map<String, ModelProperty>>(); typePropertyInfos = new HashMap<String, Map<String, ModelProperty>>(); mixinPropertyInfos = new HashMap<String, Map<String, ModelProperty>>(); proxyPropertyInfos = new HashMap<String, ModelProperty>(); sharedPropertyInfos = new HashMap<String, ModelProperty>(); mergedPropertyInfos = new HashMap<String, ModelProperty>(); schemaPathPropertyInfos = new HashMap<String, Map<String, ModelProperty>>(); prefixToSchema = new HashMap<String, String>(); schemaSimpleTextPaths = new HashMap<String, Set<String>>(); allPathPropertyInfos = new HashMap<String, ModelProperty>(); fulltextInfoByFragment = new HashMap<String, PropertyType>(); fragmentKeyTypes = new HashMap<String, Map<String, ColumnType>>(); binaryFragmentKeys = new HashMap<String, List<String>>(); collectionTables = new HashMap<String, PropertyType>(); collectionOrderBy = new HashMap<String, String>(); schemaFragments = new HashMap<String, Set<String>>(); typeFragments = new HashMap<String, Set<String>>(); mixinFragments = new HashMap<String, Set<String>>(); proxyFragments = new HashSet<String>(); docTypePrefetchedFragments = new HashMap<String, Set<String>>(); fieldFragment = new HashMap<String, String>(); schemaComplexChildren = new HashMap<String, Map<String, String>>(); typeComplexChildren = new HashMap<String, Map<String, String>>(); mixinComplexChildren = new HashMap<String, Map<String, String>>(); documentSuperTypes = new HashMap<String, String>(); documentSubTypes = new HashMap<String, Set<String>>(); specialPropertyTypes = new HashMap<String, Type>(); noPerDocumentQueryFacets = new HashSet<String>(); miscInHierarchy = false; if (repositoryDescriptor.getFulltextDescriptor().getFulltextDisabled()) { fulltextConfiguration = null; } else { fulltextConfiguration = new FulltextConfiguration(repositoryDescriptor.getFulltextDescriptor()); } initMainModel(); initVersionsModel(); if (proxiesEnabled) { initProxiesModel(); } initLocksModel(); initAclModel(); initMiscModel(); // models for all document types and mixins initModels(); if (fulltextConfiguration != null) { inferFulltextInfoByFragment(); // needs mixin schemas initFullTextModel(); } } /** * Gets the repository descriptor used for this model. * * @return the repository descriptor */ public RepositoryDescriptor getRepositoryDescriptor() { return repositoryDescriptor; } /** * Fixup an id that has been turned into a string for high-level Nuxeo APIs. * * @param id the id to fixup * @return the fixed up id */ public Serializable idFromString(String id) { switch (idType) { case STRING: return id; case LONG: // use isNumeric instead of try/catch for efficiency if (StringUtils.isNumeric(id)) { return Long.valueOf(id); } else { return NO_SUCH_LONG_ID; } default: throw new AssertionError(idType.toString()); } } /** * Turns an id that may be a String or a Long into a String for high-level Nuxeo APIs. * * @param the serializable id * @return the string */ public String idToString(Serializable id) { return id.toString(); } /** * Records info about a system property (applying to all document types). */ private void addPropertyInfo(String propertyName, PropertyType propertyType, String fragmentName, String fragmentKey, boolean readonly, Type coreType, ColumnType type) { addPropertyInfo(null, false, propertyName, propertyType, fragmentName, fragmentKey, readonly, coreType, type); } /** * Records info about one property (for a given type). Used for proxy properties. */ private void addPropertyInfo(String typeName, String propertyName, PropertyType propertyType, String fragmentName, String fragmentKey, boolean readonly, Type coreType, ColumnType type) { addPropertyInfo(typeName, false, propertyName, propertyType, fragmentName, fragmentKey, readonly, coreType, type); } /** * Records info about one property from a complex type or schema. */ private void addPropertyInfo(ComplexType complexType, String propertyName, PropertyType propertyType, String fragmentName, String fragmentKey, boolean readonly, Type coreType, ColumnType type) { String typeName = complexType.getName(); boolean isSchema = complexType instanceof Schema; addPropertyInfo(typeName, isSchema, propertyName, propertyType, fragmentName, fragmentKey, readonly, coreType, type); } /** * Records info about one property, in a schema-based structure and in a fragment-based structure. */ private void addPropertyInfo(String typeName, boolean isSchema, String propertyName, PropertyType propertyType, String fragmentName, String fragmentKey, boolean readonly, Type coreType, ColumnType type) { ModelProperty propertyInfo = new ModelProperty(propertyType, fragmentName, fragmentKey, readonly); // per type/schema property info Map<String, ModelProperty> propertyInfos; if (typeName == null) { propertyInfos = sharedPropertyInfos; } else { Map<String, Map<String, ModelProperty>> map = isSchema ? schemaPropertyInfos : typePropertyInfos; propertyInfos = map.get(typeName); if (propertyInfos == null) { map.put(typeName, propertyInfos = new HashMap<String, ModelProperty>()); } } propertyInfos.put(propertyName, propertyInfo); // merged properties ModelProperty previous = mergedPropertyInfos.get(propertyName); if (previous == null) { mergedPropertyInfos.put(propertyName, propertyInfo); } else { log.debug(String.format( "Schemas '%s' and '%s' both have a property '%s', " + "unqualified reference in queries will use schema '%1$s'", previous.fragmentName, fragmentName, propertyName)); } // compatibility for use of schema name as prefix if (typeName != null && !propertyName.contains(":")) { // allow schema name as prefix propertyName = typeName + ':' + propertyName; previous = mergedPropertyInfos.get(propertyName); if (previous == null) { mergedPropertyInfos.put(propertyName, propertyInfo); } } // system properties core type (no core schema available) if (coreType != null) { specialPropertyTypes.put(propertyName, coreType); } // per-fragment property info Map<String, ModelProperty> fragmentKeyInfos = fragmentPropertyInfos.get(fragmentName); if (fragmentKeyInfos == null) { fragmentPropertyInfos.put(fragmentName, fragmentKeyInfos = new HashMap<String, ModelProperty>()); } if (fragmentKey != null) { fragmentKeyInfos.put(fragmentKey, propertyInfo); } // per-fragment keys type if (fragmentKey != null && type != null) { Map<String, ColumnType> fragmentKeys = fragmentKeyTypes.get(fragmentName); if (fragmentKeys == null) { fragmentKeyTypes.put(fragmentName, fragmentKeys = new LinkedHashMap<String, ColumnType>()); } fragmentKeys.put(fragmentKey, type); // record binary columns for the GC if (type.spec == ColumnSpec.BLOBID) { List<String> keys = binaryFragmentKeys.get(fragmentName); if (keys == null) { binaryFragmentKeys.put(fragmentName, keys = new ArrayList<String>(1)); } keys.add(fragmentKey); } } // else collection, uses addCollectionFragmentInfos directly } private void addCollectionFragmentInfos(String fragmentName, PropertyType propertyType, String orderBy, Map<String, ColumnType> keysType) { collectionTables.put(fragmentName, propertyType); collectionOrderBy.put(fragmentName, orderBy); fragmentKeyTypes.put(fragmentName, keysType); } // fill the map for key with property infos for the given schemas // different maps are used for doctypes or mixins private void inferPropertyInfos(Map<String, Map<String, ModelProperty>> map, String key, Set<String> schemaNames) { Map<String, ModelProperty> propertyInfos = map.get(key); if (propertyInfos == null) { map.put(key, propertyInfos = new HashMap<String, ModelProperty>()); } for (String schemaName : schemaNames) { Map<String, ModelProperty> infos = schemaPropertyInfos.get(schemaName); if (infos != null) { propertyInfos.putAll(infos); } // else schema with no properties (complex list) } } /** * Infers all possible paths for properties in a schema. */ private void inferSchemaPropertyPaths(Schema schema) { String schemaName = schema.getName(); if (schemaPathPropertyInfos.containsKey(schemaName)) { return; } Map<String, ModelProperty> propertyInfoByPath = new HashMap<String, ModelProperty>(); inferTypePropertyPaths(schema, "", propertyInfoByPath, null); schemaPathPropertyInfos.put(schemaName, propertyInfoByPath); // allow schema-as-prefix if schemas has no prefix, if non-complex Map<String, ModelProperty> alsoWithPrefixes = new HashMap<String, ModelProperty>(propertyInfoByPath); String prefix = schema.getNamespace().prefix; if (prefix.isEmpty()) { for (Entry<String, ModelProperty> e : propertyInfoByPath.entrySet()) { alsoWithPrefixes.put(schemaName + ':' + e.getKey(), e.getValue()); } } else { prefixToSchema.put(prefix, schemaName); } allPathPropertyInfos.putAll(alsoWithPrefixes); // those for simpletext properties Set<String> simplePaths = new HashSet<String>(); for (Entry<String, ModelProperty> entry : propertyInfoByPath.entrySet()) { ModelProperty pi = entry.getValue(); if (pi.isIntermediateSegment()) { continue; } if (pi.propertyType != PropertyType.STRING && pi.propertyType != PropertyType.ARRAY_STRING) { continue; } simplePaths.add(entry.getKey()); } schemaSimpleTextPaths.put(schemaName, simplePaths); } // recurses in a schema or complex type private void inferTypePropertyPaths(ComplexType complexType, String prefix, Map<String, ModelProperty> propertyInfoByPath, Set<String> done) { if (done == null) { done = new LinkedHashSet<String>(); } String typeName = complexType.getName(); if (done.contains(typeName)) { log.warn("Complex type " + typeName + " refers to itself recursively: " + done); // stop recursion return; } done.add(typeName); for (Field field : complexType.getFields()) { String propertyName = field.getName().getPrefixedName(); String path = prefix + propertyName; Type fieldType = field.getType(); if (fieldType.isComplexType()) { // complex type propertyInfoByPath.put(path, new ModelProperty(propertyName)); inferTypePropertyPaths((ComplexType) fieldType, path + '/', propertyInfoByPath, done); continue; } else if (fieldType.isListType()) { Type listFieldType = ((ListType) fieldType).getFieldType(); if (!listFieldType.isSimpleType()) { // complex list propertyInfoByPath.put(path + "/*", new ModelProperty(propertyName)); inferTypePropertyPaths((ComplexType) listFieldType, path + "/*/", propertyInfoByPath, done); continue; } // else array } // else primitive type // in both cases, record it Map<String, Map<String, ModelProperty>> map = (complexType instanceof Schema) ? schemaPropertyInfos : typePropertyInfos; ModelProperty pi = map.get(typeName).get(propertyName); propertyInfoByPath.put(path, pi); // also add the propname/* path for array elements if (pi.propertyType.isArray()) { propertyInfoByPath.put(path + "/*", pi); if (!supportsArrayColumns) { // pseudo-syntax with ending "#" to get to the pos column String posPropertyName = propertyName + "#"; ModelProperty posPi = map.get(typeName).get(posPropertyName); propertyInfoByPath.put(path + "#", posPi); } } } done.remove(typeName); } private void inferFulltextInfoByFragment() { // simple fragments for (Entry<String, Map<String, ModelProperty>> es : fragmentPropertyInfos.entrySet()) { String fragmentName = es.getKey(); Map<String, ModelProperty> infos = es.getValue(); if (infos == null) { continue; } PropertyType type = null; for (ModelProperty info : infos.values()) { if (info != null && info.fulltext) { PropertyType t = info.propertyType; if (t == PropertyType.STRING || t == PropertyType.BINARY) { if (type == null) { type = t; continue; } if (type != t) { type = PropertyType.BOOLEAN; // both break; } } } } fulltextInfoByFragment.put(fragmentName, type); } // collection fragments for (Entry<String, PropertyType> es : collectionTables.entrySet()) { String fragmentName = es.getKey(); PropertyType type = es.getValue(); if (type == PropertyType.ARRAY_STRING || type == PropertyType.ARRAY_BINARY) { fulltextInfoByFragment.put(fragmentName, type.getArrayBaseType()); } } } /** Get doctype/complextype property info. */ public ModelProperty getPropertyInfo(String typeName, String propertyName) { Map<String, ModelProperty> propertyInfos = typePropertyInfos.get(typeName); if (propertyInfos == null) { // no such doctype/complextype return null; } ModelProperty propertyInfo = propertyInfos.get(propertyName); return propertyInfo != null ? propertyInfo : sharedPropertyInfos.get(propertyName); } public Map<String, ModelProperty> getMixinPropertyInfos(String mixin) { return mixinPropertyInfos.get(mixin); } // for all types for now public ModelProperty getProxySchemasPropertyInfo(String propertyName) { ModelProperty propertyInfo = proxyPropertyInfos.get(propertyName); return propertyInfo != null ? propertyInfo : sharedPropertyInfos.get(propertyName); } public ModelProperty getMixinPropertyInfo(String mixin, String propertyName) { Map<String, ModelProperty> propertyInfos = mixinPropertyInfos.get(mixin); if (propertyInfos == null) { // no such mixin return null; } return propertyInfos.get(propertyName); } public ModelProperty getPropertyInfo(String propertyName) { return mergedPropertyInfos.get(propertyName); } /** * Gets the model of the property for the given path. Returns something with * {@link ModelProperty#isIntermediateSegment()} = true for an intermediate segment of a complex property. */ public ModelProperty getPathPropertyInfo(String xpath) { return allPathPropertyInfos.get(xpath); } public Set<String> getPropertyInfoNames() { return mergedPropertyInfos.keySet(); } public ModelProperty getPathPropertyInfo(String primaryType, String[] mixinTypes, String path) { for (String schema : getAllSchemas(primaryType, mixinTypes)) { Map<String, ModelProperty> propertyInfoByPath = schemaPathPropertyInfos.get(schema); if (propertyInfoByPath != null) { ModelProperty pi = propertyInfoByPath.get(path); if (pi != null) { return pi; } } } return null; } public Map<String, String> getTypeComplexChildren(String typeName) { return typeComplexChildren.get(typeName); } public Map<String, String> getMixinComplexChildren(String mixin) { return mixinComplexChildren.get(mixin); } public Set<String> getSimpleTextPropertyPaths(String primaryType, String[] mixinTypes) { Set<String> paths = new HashSet<String>(); for (String schema : getAllSchemas(primaryType, mixinTypes)) { Set<String> p = schemaSimpleTextPaths.get(schema); if (p != null) { paths.addAll(p); } } return paths; } /** * Checks if the given xpath, when resolved on a proxy, points to a proxy-specific schema instead of the target * document. */ public boolean isProxySchemaPath(String xpath) { int p = xpath.indexOf(':'); if (p == -1) { return false; // no schema/prefix -> not on proxy } String prefix = xpath.substring(0, p); String schema = prefixToSchema.get(prefix); if (schema == null) { schema = prefix; } return allProxySchemas.contains(schema); } private Set<String> getAllSchemas(String primaryType, String[] mixinTypes) { Set<String> schemas = new LinkedHashSet<String>(); Set<String> s = allDocTypeSchemas.get(primaryType); if (s != null) { schemas.addAll(s); } for (String mixin : mixinTypes) { s = allMixinSchemas.get(mixin); if (s != null) { schemas.addAll(s); } } return schemas; } public FulltextConfiguration getFulltextConfiguration() { return fulltextConfiguration; } /** * Finds out if a field is to be indexed as fulltext. * * @param fragmentName * @param fragmentKey the key or {@code null} for a collection * @return {@link PropertyType#STRING} or {@link PropertyType#BINARY} if this field is to be indexed as fulltext */ public PropertyType getFulltextFieldType(String fragmentName, String fragmentKey) { if (fragmentKey == null) { PropertyType type = collectionTables.get(fragmentName); if (type == PropertyType.ARRAY_STRING || type == PropertyType.ARRAY_BINARY) { return type.getArrayBaseType(); } return null; } else { Map<String, ModelProperty> infos = fragmentPropertyInfos.get(fragmentName); if (infos == null) { return null; } ModelProperty info = infos.get(fragmentKey); if (info != null && info.fulltext) { return info.propertyType; } return null; } } /** * Checks if a fragment has any field indexable as fulltext. * * @param fragmentName * @return PropertyType.STRING, PropertyType.BINARY, or PropertyType.BOOLEAN for both. */ public PropertyType getFulltextInfoForFragment(String fragmentName) { return fulltextInfoByFragment.get(fragmentName); } public Type getSpecialPropertyType(String propertyName) { return specialPropertyTypes.get(propertyName); } public PropertyType getCollectionFragmentType(String fragmentName) { return collectionTables.get(fragmentName); } public boolean isCollectionFragment(String fragmentName) { return collectionTables.containsKey(fragmentName); } public String getCollectionOrderBy(String fragmentName) { return collectionOrderBy.get(fragmentName); } public Set<String> getFragmentNames() { return fragmentKeyTypes.keySet(); } public Map<String, ColumnType> getFragmentKeysType(String fragmentName) { return fragmentKeyTypes.get(fragmentName); } public Map<String, List<String>> getBinaryPropertyInfos() { return binaryFragmentKeys; } private void addTypeFragments(String typeName, Set<String> fragmentNames) { typeFragments.put(typeName, fragmentNames); } private void addFieldFragment(Field field, String fragmentName) { String fieldName = field.getName().toString(); fieldFragment.put(fieldName, fragmentName); } private void addDocTypePrefetchedFragments(String docTypeName, Set<String> fragmentNames) { Set<String> fragments = docTypePrefetchedFragments.get(docTypeName); if (fragments == null) { docTypePrefetchedFragments.put(docTypeName, fragments = new HashSet<String>()); } fragments.addAll(fragmentNames); } /** * Gets the fragments for a type (doctype or complex type). */ private Set<String> getTypeFragments(String docTypeName) { return typeFragments.get(docTypeName); } /** * Gets the fragments for a mixin. */ private Set<String> getMixinFragments(String mixin) { return mixinFragments.get(mixin); } public Set<String> getTypePrefetchedFragments(String typeName) { return docTypePrefetchedFragments.get(typeName); } /** * Checks if we have a type (doctype or complex type). */ public boolean isType(String typeName) { return typeFragments.containsKey(typeName); } public String getDocumentSuperType(String typeName) { return documentSuperTypes.get(typeName); } public Set<String> getDocumentSubTypes(String typeName) { return documentSubTypes.get(typeName); } public Set<String> getDocumentTypes() { return documentTypesMixins.keySet(); } public Set<String> getDocumentTypeFacets(String typeName) { Set<String> facets = documentTypesMixins.get(typeName); return facets == null ? Collections.<String> emptySet() : facets; } public Set<String> getMixinDocumentTypes(String mixin) { Set<String> types = mixinsDocumentTypes.get(mixin); return types == null ? Collections.<String> emptySet() : types; } /** * Given a map of id to types, returns a map of fragment names to ids. */ public Map<String, Set<Serializable>> getPerFragmentIds(Map<Serializable, IdWithTypes> idToTypes) { Map<String, Set<Serializable>> allFragmentIds = new HashMap<String, Set<Serializable>>(); for (Entry<Serializable, IdWithTypes> e : idToTypes.entrySet()) { Serializable id = e.getKey(); IdWithTypes typeInfo = e.getValue(); for (String fragmentName : getTypeFragments(typeInfo)) { Set<Serializable> fragmentIds = allFragmentIds.get(fragmentName); if (fragmentIds == null) { allFragmentIds.put(fragmentName, fragmentIds = new HashSet<Serializable>()); } fragmentIds.add(id); } } return allFragmentIds; } /** * Gets the type fragments for a primary type and mixin types. Hierarchy is included. */ public Set<String> getTypeFragments(IdWithTypes typeInfo) { Set<String> fragmentNames = new HashSet<String>(); fragmentNames.add(HIER_TABLE_NAME); Set<String> tf = getTypeFragments(typeInfo.primaryType); if (tf != null) { // null if unknown type left in the database fragmentNames.addAll(tf); } String[] mixins = typeInfo.mixinTypes; if (mixins != null) { for (String mixin : mixins) { Set<String> mf = getMixinFragments(mixin); if (mf != null) { fragmentNames.addAll(mf); } } } if (PROXY_TYPE.equals(typeInfo.primaryType)) { fragmentNames.addAll(proxyFragments); } return fragmentNames; } public Set<String> getNoPerDocumentQueryFacets() { return noPerDocumentQueryFacets; } /** * Creates all the models. */ private void initModels() { SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class); log.debug("Schemas fields from descriptor: " + repositoryDescriptor.schemaFields); // document types for (DocumentType docType : schemaManager.getDocumentTypes()) { initDocTypeOrMixinModel(docType.getName(), docType.getSchemas(), allDocTypeSchemas, typeFragments, typePropertyInfos, typeComplexChildren, true); initDocTypePrefetch(docType); initDocTypeMixins(docType); inferSuperType(docType); } // mixins for (CompositeType type : schemaManager.getFacets()) { initDocTypeOrMixinModel(type.getName(), type.getSchemas(), allMixinSchemas, mixinFragments, mixinPropertyInfos, mixinComplexChildren, false); log.debug("Fragments for facet " + type.getName() + ": " + getMixinFragments(type.getName())); } // proxy schemas initProxySchemas(schemaManager.getProxySchemas(null)); // second pass to get subtypes (needs all supertypes) for (DocumentType documentType : schemaManager.getDocumentTypes()) { inferSubTypes(documentType); } // init no per instance query facets initNoPerDocumentQueryFacets(schemaManager); } private void initProxySchemas(List<Schema> proxySchemas) { Map<String, Set<String>> allSchemas = new HashMap<String, Set<String>>(); Map<String, Set<String>> allFragments = new HashMap<String, Set<String>>(); Map<String, Map<String, String>> allChildren = new HashMap<String, Map<String, String>>(); Map<String, Map<String, ModelProperty>> allPropertyInfos = new HashMap<String, Map<String, ModelProperty>>(); String key = "__proxies__"; // not stored initDocTypeOrMixinModel(key, proxySchemas, allSchemas, allFragments, allPropertyInfos, allChildren, false); allProxySchemas.addAll(allSchemas.get(key)); proxyFragments.addAll(allFragments.get(key)); proxyPropertyInfos.putAll(allPropertyInfos.get(key)); typeComplexChildren.put(PROXY_TYPE, allChildren.get(key)); } private Set<String> getCommonFragments(String typeName) { Set<String> fragments = new HashSet<String>(5); fragments.add(VERSION_TABLE_NAME); fragments.add(ACL_TABLE_NAME); if (!miscInHierarchy) { fragments.add(MISC_TABLE_NAME); } if (fulltextConfiguration != null && fulltextConfiguration.isFulltextIndexable(typeName)) { fragments.add(FULLTEXT_TABLE_NAME); } return fragments; } private Set<String> getCommonFragmentsPrefetched() { Set<String> fragments = new HashSet<String>(5); fragments.add(VERSION_TABLE_NAME); fragments.add(ACL_TABLE_NAME); if (!miscInHierarchy) { fragments.add(MISC_TABLE_NAME); } return fragments; } /** * For a doctype or mixin type, init the schemas-related structures. */ private void initDocTypeOrMixinModel(String typeName, Collection<Schema> schemas, Map<String, Set<String>> schemasMap, Map<String, Set<String>> fragmentsMap, Map<String, Map<String, ModelProperty>> propertyInfoMap, Map<String, Map<String, String>> complexChildrenMap, boolean addCommonFragments) { Set<String> schemaNames = new HashSet<String>(); Set<String> fragmentNames = new HashSet<String>(); Map<String, String> complexChildren = new HashMap<String, String>(); if (addCommonFragments) { fragmentNames.addAll(getCommonFragments(typeName)); } for (Schema schema : schemas) { if (schema == null) { // happens when a type refers to a nonexistent schema // TODO log and avoid nulls earlier continue; } schemaNames.add(schema.getName()); try { fragmentNames.addAll(initSchemaModel(schema)); } catch (NuxeoException e) { e.addInfo(String.format("Error initializing schema '%s' for composite type '%s'", schema.getName(), typeName)); throw e; } inferSchemaPropertyPaths(schema); complexChildren.putAll(schemaComplexChildren.get(schema.getName())); } schemasMap.put(typeName, schemaNames); fragmentsMap.put(typeName, fragmentNames); complexChildrenMap.put(typeName, complexChildren); inferPropertyInfos(propertyInfoMap, typeName, schemaNames); } private void initDocTypePrefetch(DocumentType docType) { String docTypeName = docType.getName(); PrefetchInfo prefetch = docType.getPrefetchInfo(); if (prefetch != null) { Set<String> documentTypeFragments = getTypeFragments(docTypeName); for (String fieldName : prefetch.getFields()) { // prefetch all the relevant fragments // TODO deal with full xpath String fragment = fieldFragment.get(fieldName); if (fragment != null) { // checks that the field actually belongs // to the type if (documentTypeFragments.contains(fragment)) { addDocTypePrefetchedFragments(docTypeName, Collections.singleton(fragment)); } } } for (String schemaName : prefetch.getSchemas()) { Set<String> fragments = schemaFragments.get(schemaName); if (fragments != null) { addDocTypePrefetchedFragments(docTypeName, fragments); } } } // always prefetch ACLs, versions, misc (for lifecycle) addDocTypePrefetchedFragments(docTypeName, getCommonFragmentsPrefetched()); log.debug("Fragments for type " + docTypeName + ": " + getTypeFragments(docTypeName) + ", prefetch: " + getTypePrefetchedFragments(docTypeName)); } private void initDocTypeMixins(DocumentType docType) { String docTypeName = docType.getName(); Set<String> mixins = docType.getFacets(); documentTypesMixins.put(docTypeName, new HashSet<String>(mixins)); for (String mixin : mixins) { Set<String> mixinTypes = mixinsDocumentTypes.get(mixin); if (mixinTypes == null) { mixinsDocumentTypes.put(mixin, mixinTypes = new HashSet<String>()); } mixinTypes.add(docTypeName); } } private void inferSuperType(DocumentType docType) { Type superType = docType.getSuperType(); if (superType != null) { documentSuperTypes.put(docType.getName(), superType.getName()); } } private void inferSubTypes(DocumentType docType) { String type = docType.getName(); String superType = type; do { Set<String> subTypes = documentSubTypes.get(superType); if (subTypes == null) { documentSubTypes.put(superType, subTypes = new HashSet<String>()); } subTypes.add(type); superType = documentSuperTypes.get(superType); } while (superType != null); } private void initNoPerDocumentQueryFacets(SchemaManager schemaManager) { noPerDocumentQueryFacets.addAll(schemaManager.getNoPerDocumentQueryFacets()); } /** * Special model for the main table (the one containing the primary type information). * <p> * If the main table is not separate from the hierarchy table, then it's will not really be instantiated by itself * but merged into the hierarchy table. */ private void initMainModel() { addPropertyInfo(MAIN_PRIMARY_TYPE_PROP, PropertyType.STRING, HIER_TABLE_NAME, MAIN_PRIMARY_TYPE_KEY, true, null, ColumnType.SYSNAME); addPropertyInfo(MAIN_MIXIN_TYPES_PROP, PropertyType.STRING, HIER_TABLE_NAME, MAIN_MIXIN_TYPES_KEY, false, null, ColumnType.SYSNAMEARRAY); addPropertyInfo(MAIN_CHECKED_IN_PROP, PropertyType.BOOLEAN, HIER_TABLE_NAME, MAIN_CHECKED_IN_KEY, false, BooleanType.INSTANCE, ColumnType.BOOLEAN); addPropertyInfo(MAIN_BASE_VERSION_PROP, idPropertyType, HIER_TABLE_NAME, MAIN_BASE_VERSION_KEY, false, idCoreType, ColumnType.NODEVAL); addPropertyInfo(MAIN_MAJOR_VERSION_PROP, PropertyType.LONG, HIER_TABLE_NAME, MAIN_MAJOR_VERSION_KEY, false, LongType.INSTANCE, ColumnType.INTEGER); addPropertyInfo(MAIN_MINOR_VERSION_PROP, PropertyType.LONG, HIER_TABLE_NAME, MAIN_MINOR_VERSION_KEY, false, LongType.INSTANCE, ColumnType.INTEGER); addPropertyInfo(MAIN_IS_VERSION_PROP, PropertyType.BOOLEAN, HIER_TABLE_NAME, MAIN_IS_VERSION_KEY, false, BooleanType.INSTANCE, ColumnType.BOOLEAN); addPropertyInfo(MAIN_CHANGE_TOKEN_PROP, PropertyType.STRING, HIER_TABLE_NAME, MAIN_CHANGE_TOKEN_KEY, false, StringType.INSTANCE, ColumnType.SYSNAME); if (softDeleteEnabled) { addPropertyInfo(MAIN_IS_DELETED_PROP, PropertyType.BOOLEAN, HIER_TABLE_NAME, MAIN_IS_DELETED_KEY, true, BooleanType.INSTANCE, ColumnType.BOOLEAN); addPropertyInfo(MAIN_DELETED_TIME_PROP, PropertyType.DATETIME, HIER_TABLE_NAME, MAIN_DELETED_TIME_KEY, true, DateType.INSTANCE, ColumnType.TIMESTAMP); } } /** * Special model for the "misc" table (lifecycle, dirty.). */ private void initMiscModel() { String fragmentName = miscInHierarchy ? HIER_TABLE_NAME : MISC_TABLE_NAME; addPropertyInfo(MISC_LIFECYCLE_POLICY_PROP, PropertyType.STRING, fragmentName, MISC_LIFECYCLE_POLICY_KEY, false, StringType.INSTANCE, ColumnType.SYSNAME); addPropertyInfo(MISC_LIFECYCLE_STATE_PROP, PropertyType.STRING, fragmentName, MISC_LIFECYCLE_STATE_KEY, false, StringType.INSTANCE, ColumnType.SYSNAME); } /** * Special model for the versions table. */ private void initVersionsModel() { addPropertyInfo(VERSION_VERSIONABLE_PROP, idPropertyType, VERSION_TABLE_NAME, VERSION_VERSIONABLE_KEY, false, idCoreType, ColumnType.NODEVAL); addPropertyInfo(VERSION_CREATED_PROP, PropertyType.DATETIME, VERSION_TABLE_NAME, VERSION_CREATED_KEY, false, DateType.INSTANCE, ColumnType.TIMESTAMP); addPropertyInfo(VERSION_LABEL_PROP, PropertyType.STRING, VERSION_TABLE_NAME, VERSION_LABEL_KEY, false, StringType.INSTANCE, ColumnType.SYSNAME); addPropertyInfo(VERSION_DESCRIPTION_PROP, PropertyType.STRING, VERSION_TABLE_NAME, VERSION_DESCRIPTION_KEY, false, StringType.INSTANCE, ColumnType.STRING); addPropertyInfo(VERSION_IS_LATEST_PROP, PropertyType.BOOLEAN, VERSION_TABLE_NAME, VERSION_IS_LATEST_KEY, false, BooleanType.INSTANCE, ColumnType.BOOLEAN); addPropertyInfo(VERSION_IS_LATEST_MAJOR_PROP, PropertyType.BOOLEAN, VERSION_TABLE_NAME, VERSION_IS_LATEST_MAJOR_KEY, false, BooleanType.INSTANCE, ColumnType.BOOLEAN); } /** * Special model for the proxies table. */ private void initProxiesModel() { String type = PROXY_TYPE; addPropertyInfo(type, PROXY_TARGET_PROP, idPropertyType, PROXY_TABLE_NAME, PROXY_TARGET_KEY, false, idCoreType, ColumnType.NODEIDFKNP); addPropertyInfo(type, PROXY_VERSIONABLE_PROP, idPropertyType, PROXY_TABLE_NAME, PROXY_VERSIONABLE_KEY, false, idCoreType, ColumnType.NODEVAL); addTypeFragments(type, Collections.singleton(PROXY_TABLE_NAME)); } /** * Special model for the locks table (also, primary key has no foreign key, see {@link SQLInfo#initFragmentSQL}. */ private void initLocksModel() { addPropertyInfo(LOCK_OWNER_PROP, PropertyType.STRING, LOCK_TABLE_NAME, LOCK_OWNER_KEY, false, StringType.INSTANCE, ColumnType.SYSNAME); addPropertyInfo(LOCK_CREATED_PROP, PropertyType.DATETIME, LOCK_TABLE_NAME, LOCK_CREATED_KEY, false, DateType.INSTANCE, ColumnType.TIMESTAMP); } /** * Special model for the fulltext table. */ private void initFullTextModel() { addPropertyInfo(FULLTEXT_JOBID_PROP, PropertyType.STRING, FULLTEXT_TABLE_NAME, FULLTEXT_JOBID_KEY, false, StringType.INSTANCE, ColumnType.SYSNAME); for (String indexName : fulltextConfiguration.indexNames) { String suffix = getFulltextIndexSuffix(indexName); if (materializeFulltextSyntheticColumn) { addPropertyInfo(FULLTEXT_FULLTEXT_PROP + suffix, PropertyType.STRING, FULLTEXT_TABLE_NAME, FULLTEXT_FULLTEXT_KEY + suffix, false, StringType.INSTANCE, ColumnType.FTINDEXED); } addPropertyInfo(FULLTEXT_SIMPLETEXT_PROP + suffix, PropertyType.STRING, FULLTEXT_TABLE_NAME, FULLTEXT_SIMPLETEXT_KEY + suffix, false, StringType.INSTANCE, ColumnType.FTSTORED); addPropertyInfo(FULLTEXT_BINARYTEXT_PROP + suffix, PropertyType.STRING, FULLTEXT_TABLE_NAME, FULLTEXT_BINARYTEXT_KEY + suffix, false, StringType.INSTANCE, ColumnType.FTSTORED); } } public String getFulltextIndexSuffix(String indexName) { return indexName.equals(FULLTEXT_DEFAULT_INDEX) ? "" : '_' + indexName; } /** * Special collection-like model for the ACL table. */ private void initAclModel() { Map<String, ColumnType> keysType = new LinkedHashMap<String, ColumnType>(); keysType.put(ACL_POS_KEY, ColumnType.INTEGER); keysType.put(ACL_NAME_KEY, ColumnType.SYSNAME); keysType.put(ACL_GRANT_KEY, ColumnType.BOOLEAN); keysType.put(ACL_PERMISSION_KEY, ColumnType.SYSNAME); keysType.put(ACL_CREATOR_KEY, ColumnType.SYSNAME); keysType.put(ACL_BEGIN_KEY, ColumnType.TIMESTAMP); keysType.put(ACL_END_KEY, ColumnType.TIMESTAMP); keysType.put(ACL_STATUS_KEY, ColumnType.LONG); keysType.put(ACL_USER_KEY, ColumnType.SYSNAME); keysType.put(ACL_GROUP_KEY, ColumnType.SYSNAME); String fragmentName = ACL_TABLE_NAME; addCollectionFragmentInfos(fragmentName, PropertyType.COLL_ACL, ACL_POS_KEY, keysType); addPropertyInfo(ACL_PROP, PropertyType.COLL_ACL, fragmentName, null, false, null, null); // for query // composed of NXQL.ECM_ACL and NXQL.ECM_ACL_PRINCIPAL etc. allPathPropertyInfos.put("ecm:acl.principal/*", new ModelProperty(PropertyType.STRING, fragmentName, ACL_USER_KEY, true)); allPathPropertyInfos.put("ecm:acl.permission/*", new ModelProperty(PropertyType.STRING, fragmentName, ACL_PERMISSION_KEY, true)); allPathPropertyInfos.put("ecm:acl.grant/*", new ModelProperty(PropertyType.BOOLEAN, fragmentName, ACL_GRANT_KEY, true)); allPathPropertyInfos.put("ecm:acl.name/*", new ModelProperty(PropertyType.STRING, fragmentName, ACL_NAME_KEY, true)); allPathPropertyInfos.put("ecm:acl.pos/*", new ModelProperty(PropertyType.LONG, fragmentName, ACL_POS_KEY, true)); allPathPropertyInfos.put("ecm:acl.creator/*", new ModelProperty(PropertyType.STRING, fragmentName, ACL_CREATOR_KEY, true)); allPathPropertyInfos.put("ecm:acl.begin/*", new ModelProperty(PropertyType.DATETIME, fragmentName, ACL_BEGIN_KEY, true)); allPathPropertyInfos.put("ecm:acl.end/*", new ModelProperty(PropertyType.DATETIME, fragmentName, ACL_END_KEY, true)); allPathPropertyInfos.put("ecm:acl.status/*", new ModelProperty(PropertyType.LONG, fragmentName, ACL_STATUS_KEY, true)); } /** * Creates the model for a schema. */ private Set<String> initSchemaModel(Schema schema) { initComplexTypeModel(schema); return schemaFragments.get(schema.getName()); } /** * Creates the model for a complex type or a schema. Recurses in complex types. * <p> * Adds the simple+collection fragments to {@link #typeFragments} or {@link #schemaFragments}. */ private void initComplexTypeModel(ComplexType complexType) { String typeName = complexType.getName(); boolean isSchema = complexType instanceof Schema; if (isSchema && schemaFragments.containsKey(typeName)) { return; } else if (!isSchema && typeFragments.containsKey(typeName)) { return; } /** The fragment names to use for this type, usually just one. */ Set<String> fragmentNames = new HashSet<String>(1); /** The children complex properties for this type. */ Map<String, String> complexChildren = new HashMap<String, String>(1); log.debug("Making model for type " + typeName); /** Initialized if this type has a table associated. */ for (Field field : complexType.getFields()) { Type fieldType = field.getType(); if (fieldType.isComplexType()) { /* * Complex type. */ String propertyName = field.getName().getPrefixedName(); complexChildren.put(propertyName, fieldType.getName()); initComplexTypeModel((ComplexType) fieldType); } else { String propertyName = field.getName().getPrefixedName(); FieldDescriptor fieldDescriptor = null; for (FieldDescriptor fd : repositoryDescriptor.schemaFields) { if (propertyName.equals(fd.field)) { fieldDescriptor = fd; break; } } if (fieldType.isListType()) { Type listFieldType = ((ListType) fieldType).getFieldType(); if (listFieldType.isSimpleType()) { /* * Simple list. */ PropertyType propertyType = PropertyType.fromFieldType(listFieldType, true); boolean useArray = false; ColumnType columnType = null; if (repositoryDescriptor.getArrayColumns() && fieldDescriptor == null) { fieldDescriptor = new FieldDescriptor(); fieldDescriptor.type = FIELD_TYPE_ARRAY; } if (fieldDescriptor != null) { if (FIELD_TYPE_ARRAY.equals(fieldDescriptor.type)) { if (!supportsArrayColumns) { log.warn(" Field '" + propertyName + "' array specification is ignored since" + " this database does not support arrays"); } useArray = supportsArrayColumns; columnType = ColumnType.fromFieldType(listFieldType, useArray); } else if (FIELD_TYPE_ARRAY_LARGETEXT.equals(fieldDescriptor.type)) { boolean isStringColSpec = ColumnType.fromFieldType( listFieldType).spec == ColumnSpec.STRING; if (supportsArrayColumns && !isStringColSpec) { log.warn(" Field '" + propertyName + "' is not a String yet it is specified" + " as array_largetext, using ARRAY_CLOB for it"); } else if (!supportsArrayColumns && isStringColSpec) { log.warn(" Field '" + propertyName + "' array specification is ignored since" + " this database does not support arrays," + " using CLOB for it"); } else if (!supportsArrayColumns && !isStringColSpec) { log.warn(" Field '" + propertyName + "' array specification is ignored since" + " this database does not support arrays, also" + " Field is not a String yet it is specified" + " as array_largetext, using CLOB for it"); } useArray = supportsArrayColumns; columnType = (supportsArrayColumns) ? ColumnType.ARRAY_CLOB : ColumnType.CLOB; } else if (FIELD_TYPE_LARGETEXT.equals(fieldDescriptor.type)) { if (ColumnType.fromFieldType(listFieldType).spec != ColumnSpec.STRING) { log.warn(" Field '" + propertyName + "' is not a String yet it is specified " + " as largetext, using CLOB for it"); } columnType = ColumnType.CLOB; } else { log.warn(" Field '" + propertyName + "' specified but not successfully mapped"); } } if (columnType == null) { columnType = ColumnType.fromFieldType(listFieldType); } log.debug(" List field '" + propertyName + "' using column type " + columnType); if (useArray) { /* * Array: use an array. */ String fragmentName = typeFragmentName(complexType); String fragmentKey = field.getName().getLocalName(); addPropertyInfo(complexType, propertyName, propertyType, fragmentName, fragmentKey, false, null, columnType); addFieldFragment(field, fragmentName); } else { /* * Array: use a collection table. */ String fragmentName = collectionFragmentName(propertyName); addPropertyInfo(complexType, propertyName, propertyType, fragmentName, COLL_TABLE_VALUE_KEY, false, null, columnType); // pseudo-syntax with ending "#" to get to the pos column String posPropertyName = propertyName + "#"; PropertyType posPropertyType = PropertyType.LONG; addPropertyInfo(complexType, posPropertyName, posPropertyType, fragmentName, COLL_TABLE_POS_KEY, false, null, ColumnType.INTEGER); Map<String, ColumnType> keysType = new LinkedHashMap<String, ColumnType>(); keysType.put(COLL_TABLE_POS_KEY, ColumnType.INTEGER); keysType.put(COLL_TABLE_VALUE_KEY, columnType); addCollectionFragmentInfos(fragmentName, propertyType, COLL_TABLE_POS_KEY, keysType); fragmentNames.add(fragmentName); addFieldFragment(field, fragmentName); } } else { /* * Complex list. */ initComplexTypeModel((ComplexType) listFieldType); } } else { /* * Primitive type. */ String fragmentName = typeFragmentName(complexType); String fragmentKey = field.getName().getLocalName(); PropertyType propertyType = PropertyType.fromFieldType(fieldType, false); ColumnType type = ColumnType.fromField(field); if (type.spec == ColumnSpec.STRING) { // backward compat with largetext, since 5.4.2 if (fieldDescriptor != null && FIELD_TYPE_LARGETEXT.equals(fieldDescriptor.type)) { if (!type.isUnconstrained() && !type.isClob()) { log.warn(" String field '" + propertyName + "' has a schema constraint to " + type + " but is specified as largetext," + " using CLOB for it"); } type = ColumnType.CLOB; } } if (fieldDescriptor != null) { if (fieldDescriptor.table != null) { fragmentName = fieldDescriptor.table; } if (fieldDescriptor.column != null) { fragmentKey = fieldDescriptor.column; } } if (MAIN_KEY.equalsIgnoreCase(fragmentKey)) { String msg = "A property cannot be named '" + fragmentKey + "' because this is a reserved name, in type: " + typeName; throw new NuxeoException(msg); } if (fragmentName.equals(UID_SCHEMA_NAME) && (fragmentKey.equals(UID_MAJOR_VERSION_KEY) || fragmentKey.equals(UID_MINOR_VERSION_KEY))) { // workaround: special-case the "uid" schema, put // major/minor in the hierarchy table fragmentName = HIER_TABLE_NAME; fragmentKey = fragmentKey.equals(UID_MAJOR_VERSION_KEY) ? MAIN_MAJOR_VERSION_KEY : MAIN_MINOR_VERSION_KEY; } addPropertyInfo(complexType, propertyName, propertyType, fragmentName, fragmentKey, false, null, type); if (!fragmentName.equals(HIER_TABLE_NAME)) { fragmentNames.add(fragmentName); addFieldFragment(field, fragmentName); } } } } if (isSchema) { schemaFragments.put(typeName, fragmentNames); schemaComplexChildren.put(typeName, complexChildren); } else { addTypeFragments(typeName, fragmentNames); typeComplexChildren.put(typeName, complexChildren); } } private static String typeFragmentName(ComplexType type) { return type.getName(); } private static String collectionFragmentName(String propertyName) { return propertyName; } }