001/* 002 * (C) Copyright 2006-2018 Nuxeo (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 * Florent Guillaume 018 */ 019 020package org.nuxeo.ecm.core.storage.sql; 021 022import java.io.Serializable; 023import java.util.ArrayList; 024import java.util.Collection; 025import java.util.Collections; 026import java.util.HashMap; 027import java.util.HashSet; 028import java.util.LinkedHashMap; 029import java.util.LinkedHashSet; 030import java.util.List; 031import java.util.Map; 032import java.util.Map.Entry; 033import java.util.Set; 034 035import org.apache.commons.lang3.StringUtils; 036import org.apache.commons.logging.Log; 037import org.apache.commons.logging.LogFactory; 038import org.nuxeo.ecm.core.api.NuxeoException; 039import org.nuxeo.ecm.core.schema.DocumentType; 040import org.nuxeo.ecm.core.schema.PrefetchInfo; 041import org.nuxeo.ecm.core.schema.SchemaManager; 042import org.nuxeo.ecm.core.schema.types.ComplexType; 043import org.nuxeo.ecm.core.schema.types.CompositeType; 044import org.nuxeo.ecm.core.schema.types.Field; 045import org.nuxeo.ecm.core.schema.types.ListType; 046import org.nuxeo.ecm.core.schema.types.Schema; 047import org.nuxeo.ecm.core.schema.types.Type; 048import org.nuxeo.ecm.core.schema.types.primitives.BooleanType; 049import org.nuxeo.ecm.core.schema.types.primitives.DateType; 050import org.nuxeo.ecm.core.schema.types.primitives.LongType; 051import org.nuxeo.ecm.core.schema.types.primitives.StringType; 052import org.nuxeo.ecm.core.storage.FulltextConfiguration; 053import org.nuxeo.ecm.core.storage.sql.RepositoryDescriptor.FieldDescriptor; 054import org.nuxeo.ecm.core.storage.sql.RowMapper.IdWithTypes; 055import org.nuxeo.ecm.core.storage.sql.jdbc.SQLInfo; 056import org.nuxeo.runtime.api.Framework; 057 058/** 059 * The {@link Model} is the link between high-level types and SQL-level objects (entity tables, collections). It defines 060 * all policies relating to the choice of structure (what schema are grouped together in for optimization) and names in 061 * the SQL database (table names, column names), and to what entity names (type name, field name) they correspond. 062 * <p> 063 * A Nuxeo schema or type is mapped to a SQL-level table. Several types can be aggregated in the same table. In theory, 064 * a type could even be split into different tables. 065 * 066 * @author Florent Guillaume 067 */ 068public class Model { 069 070 private static final Log log = LogFactory.getLog(Model.class); 071 072 public static final String ROOT_TYPE = "Root"; 073 074 public static final String REPOINFO_TABLE_NAME = "repositories"; 075 076 public static final String REPOINFO_REPONAME_KEY = "name"; 077 078 public static final String MAIN_KEY = "id"; 079 080 public static final String CLUSTER_NODES_TABLE_NAME = "cluster_nodes"; 081 082 public static final String CLUSTER_NODES_NODEID_KEY = "nodeid"; 083 084 public static final String CLUSTER_NODES_CREATED_KEY = "created"; 085 086 public static final String CLUSTER_INVALS_TABLE_NAME = "cluster_invals"; 087 088 public static final String CLUSTER_INVALS_NODEID_KEY = "nodeid"; 089 090 public static final String CLUSTER_INVALS_ID_KEY = "id"; 091 092 public static final String CLUSTER_INVALS_FRAGMENTS_KEY = "fragments"; 093 094 public static final String CLUSTER_INVALS_KIND_KEY = "kind"; 095 096 public static final String MAIN_PRIMARY_TYPE_PROP = "ecm:primaryType"; 097 098 public static final String MAIN_PRIMARY_TYPE_KEY = "primarytype"; 099 100 public static final String MAIN_MIXIN_TYPES_PROP = "ecm:mixinTypes"; 101 102 public static final String MAIN_MIXIN_TYPES_KEY = "mixintypes"; 103 104 public static final String MAIN_IS_RETENTION_ACTIVE_PROP = "ecm:isRetentionActive"; 105 106 public static final String MAIN_IS_RETENTION_ACTIVE_KEY = "isretentionactive"; 107 108 public static final String MAIN_BASE_VERSION_PROP = "ecm:baseVersion"; 109 110 public static final String MAIN_BASE_VERSION_KEY = "baseversionid"; 111 112 public static final String MAIN_CHECKED_IN_PROP = "ecm:isCheckedIn"; 113 114 public static final String MAIN_CHECKED_IN_KEY = "ischeckedin"; 115 116 public static final String MAIN_MAJOR_VERSION_PROP = "ecm:majorVersion"; 117 118 public static final String MAIN_MAJOR_VERSION_KEY = "majorversion"; 119 120 public static final String MAIN_MINOR_VERSION_PROP = "ecm:minorVersion"; 121 122 public static final String MAIN_MINOR_VERSION_KEY = "minorversion"; 123 124 public static final String MAIN_IS_VERSION_PROP = "ecm:isVersion"; 125 126 public static final String MAIN_IS_VERSION_KEY = "isversion"; 127 128 public static final String MAIN_SYS_CHANGE_TOKEN_PROP = "ecm:systemChangeToken"; 129 130 public static final String MAIN_SYS_CHANGE_TOKEN_KEY = "systemchangetoken"; 131 132 public static final String MAIN_CHANGE_TOKEN_PROP = "ecm:changeToken"; 133 134 public static final String MAIN_CHANGE_TOKEN_KEY = "changetoken"; 135 136 // for soft-delete 137 public static final String MAIN_IS_DELETED_PROP = "ecm:isDeleted"; 138 139 // for soft-delete 140 public static final String MAIN_IS_DELETED_KEY = "isdeleted"; 141 142 // for soft-delete 143 public static final String MAIN_DELETED_TIME_PROP = "ecm:deletedTime"; 144 145 // for soft-delete 146 public static final String MAIN_DELETED_TIME_KEY = "deletedtime"; 147 148 /** 149 * @since 10.1 150 */ 151 public static final String MAIN_IS_TRASHED_PROP = "ecm:isTrashed"; 152 153 /** 154 * @since 10.1 155 */ 156 public static final String MAIN_IS_TRASHED_KEY = "istrashed"; 157 158 public static final String UID_SCHEMA_NAME = "uid"; 159 160 public static final String UID_MAJOR_VERSION_KEY = "major_version"; 161 162 public static final String UID_MINOR_VERSION_KEY = "minor_version"; 163 164 public static final String HIER_TABLE_NAME = "hierarchy"; 165 166 public static final String HIER_PARENT_KEY = "parentid"; 167 168 public static final String HIER_CHILD_NAME_KEY = "name"; 169 170 public static final String HIER_CHILD_POS_KEY = "pos"; 171 172 public static final String HIER_CHILD_ISPROPERTY_KEY = "isproperty"; 173 174 public static final String ANCESTORS_TABLE_NAME = "ancestors"; 175 176 public static final String ANCESTORS_ANCESTOR_KEY = "ancestors"; 177 178 public static final String COLL_TABLE_POS_KEY = "pos"; 179 180 public static final String COLL_TABLE_VALUE_KEY = "item"; 181 182 public static final String MISC_TABLE_NAME = "misc"; 183 184 public static final String MISC_LIFECYCLE_POLICY_PROP = "ecm:lifeCyclePolicy"; 185 186 public static final String MISC_LIFECYCLE_POLICY_KEY = "lifecyclepolicy"; 187 188 public static final String MISC_LIFECYCLE_STATE_PROP = "ecm:lifeCycleState"; 189 190 public static final String MISC_LIFECYCLE_STATE_KEY = "lifecyclestate"; 191 192 public static final String ACL_TABLE_NAME = "acls"; 193 194 public static final String ACL_PROP = "ecm:acl"; 195 196 public static final String ACL_POS_KEY = "pos"; 197 198 public static final String ACL_NAME_KEY = "name"; 199 200 public static final String ACL_GRANT_KEY = "grant"; 201 202 public static final String ACL_PERMISSION_KEY = "permission"; 203 204 public static final String ACL_CREATOR_KEY = "creator"; 205 206 public static final String ACL_BEGIN_KEY = "begin"; 207 208 public static final String ACL_END_KEY = "end"; 209 210 public static final String ACL_STATUS_KEY = "status"; 211 212 public static final String ACL_USER_KEY = "user"; 213 214 public static final String ACL_GROUP_KEY = "group"; 215 216 public static final String VERSION_TABLE_NAME = "versions"; 217 218 public static final String VERSION_VERSIONABLE_PROP = "ecm:versionableId"; 219 220 public static final String VERSION_VERSIONABLE_KEY = "versionableid"; 221 222 public static final String VERSION_CREATED_PROP = "ecm:versionCreated"; 223 224 public static final String VERSION_CREATED_KEY = "created"; 225 226 public static final String VERSION_LABEL_PROP = "ecm:versionLabel"; 227 228 public static final String VERSION_LABEL_KEY = "label"; 229 230 public static final String VERSION_DESCRIPTION_PROP = "ecm:versionDescription"; 231 232 public static final String VERSION_DESCRIPTION_KEY = "description"; 233 234 public static final String VERSION_IS_LATEST_PROP = "ecm:isLatestVersion"; 235 236 public static final String VERSION_IS_LATEST_KEY = "islatest"; 237 238 public static final String VERSION_IS_LATEST_MAJOR_PROP = "ecm:isLatestMajorVersion"; 239 240 public static final String VERSION_IS_LATEST_MAJOR_KEY = "islatestmajor"; 241 242 public static final String PROXY_TYPE = "ecm:proxy"; 243 244 public static final String PROXY_TABLE_NAME = "proxies"; 245 246 public static final String PROXY_TARGET_PROP = "ecm:proxyTargetId"; 247 248 public static final String PROXY_TARGET_KEY = "targetid"; 249 250 public static final String PROXY_VERSIONABLE_PROP = "ecm:proxyVersionableId"; 251 252 public static final String PROXY_VERSIONABLE_KEY = "versionableid"; 253 254 public static final String LOCK_TABLE_NAME = "locks"; 255 256 public static final String LOCK_OWNER_PROP = "ecm:lockOwner"; 257 258 public static final String LOCK_OWNER_KEY = "owner"; 259 260 public static final String LOCK_CREATED_PROP = "ecm:lockCreated"; 261 262 public static final String LOCK_CREATED_KEY = "created"; 263 264 public static final String FULLTEXT_DEFAULT_INDEX = "default"; // not 265 // config 266 267 public static final String FULLTEXT_TABLE_NAME = "fulltext"; 268 269 public static final String FULLTEXT_JOBID_PROP = "ecm:fulltextJobId"; 270 271 public static final String FULLTEXT_JOBID_KEY = "jobid"; 272 273 public static final String FULLTEXT_FULLTEXT_PROP = "ecm:fulltext"; 274 275 public static final String FULLTEXT_FULLTEXT_KEY = "fulltext"; 276 277 public static final String FULLTEXT_SIMPLETEXT_PROP = "ecm:simpleText"; 278 279 public static final String FULLTEXT_SIMPLETEXT_KEY = "simpletext"; 280 281 public static final String FULLTEXT_BINARYTEXT_PROP = "ecm:binaryText"; 282 283 public static final String FULLTEXT_BINARYTEXT_KEY = "binarytext"; 284 285 public static final String HIER_READ_ACL_TABLE_NAME = "hierarchy_read_acl"; 286 287 public static final String HIER_READ_ACL_ID = "id"; 288 289 public static final String HIER_READ_ACL_ACL_ID = "acl_id"; 290 291 public static final String ACLR_USER_MAP_TABLE_NAME = "aclr_user_map"; 292 293 public static final String ACLR_USER_MAP_USER_ID = "user_id"; 294 295 public static final String ACLR_USER_MAP_ACL_ID = "acl_id"; 296 297 /** Specified in ext. point to use CLOBs. */ 298 public static final String FIELD_TYPE_LARGETEXT = "largetext"; 299 300 /** Specified in ext. point to use array instead of collection table. */ 301 public static final String FIELD_TYPE_ARRAY = "array"; 302 303 /** Specified in ext. point to use CLOB array instead of collection table. */ 304 public static final String FIELD_TYPE_ARRAY_LARGETEXT = "array_largetext"; 305 306 // some random long that's not in the database 307 // first half of md5 of "nosuchlongid" 308 public static final Long NO_SUCH_LONG_ID = Long.valueOf(0x3153147dd69fcea4L); 309 310 public static final Long INITIAL_CHANGE_TOKEN = Long.valueOf(0); 311 312 public static final Long INITIAL_SYS_CHANGE_TOKEN = Long.valueOf(0); 313 314 protected final boolean softDeleteEnabled; 315 316 protected final boolean proxiesEnabled; 317 318 protected final boolean changeTokenEnabled; 319 320 /** Type of ids as seen by the VCS Java layer. */ 321 public enum IdType { 322 STRING, // 323 LONG, // 324 } 325 326 // type of id seen by the VCS Java layer 327 protected final IdType idType; 328 329 // type for VCS row storage 330 protected final PropertyType idPropertyType; 331 332 // type for core properties 333 protected final Type idCoreType; 334 335 /** 336 * If true, the misc columns are added to hierarchy, not to a separate misc table. 337 */ 338 protected final boolean miscInHierarchy; 339 340 protected final RepositoryDescriptor repositoryDescriptor; 341 342 /** Per-doctype list of schemas. */ 343 private final Map<String, Set<String>> allDocTypeSchemas; 344 345 /** Per-mixin list of schemas. */ 346 private final Map<String, Set<String>> allMixinSchemas; 347 348 /** The proxy schemas. */ 349 private final Set<String> allProxySchemas; 350 351 /** Map of mixin to doctypes. */ 352 private final Map<String, Set<String>> mixinsDocumentTypes; 353 354 /** Map of doctype to mixins, for search. */ 355 protected final Map<String, Set<String>> documentTypesMixins; 356 357 /** Shared high-level properties that don't come from the schema manager. */ 358 private final Map<String, Type> specialPropertyTypes; 359 360 /** Map of fragment to key to property info. */ 361 private final Map<String, Map<String, ModelProperty>> fragmentPropertyInfos; 362 363 /** Map of schema to property to property info. */ 364 private final Map<String, Map<String, ModelProperty>> schemaPropertyInfos; 365 366 /** Map of docType/complexType to property to property info. */ 367 private final Map<String, Map<String, ModelProperty>> typePropertyInfos; 368 369 /** Map of mixin to property to property info. */ 370 private final Map<String, Map<String, ModelProperty>> mixinPropertyInfos; 371 372 /** The proxy property infos. */ 373 private final Map<String, ModelProperty> proxyPropertyInfos; 374 375 /** Map of property to property info. */ 376 private final Map<String, ModelProperty> sharedPropertyInfos; 377 378 /** Merged properties (all schemas together + shared). */ 379 private final Map<String, ModelProperty> mergedPropertyInfos; 380 381 /** Per-schema map of path to property info. */ 382 private final Map<String, Map<String, ModelProperty>> schemaPathPropertyInfos; 383 384 /** Map of prefix to schema. */ 385 private final Map<String, String> prefixToSchema; 386 387 /** Per-schema set of path to simple fulltext properties. */ 388 private final Map<String, Set<String>> schemaSimpleTextPaths; 389 390 /** 391 * Map of path (from all doc types) to property info. Value is NONE for valid complex property path prefixes. 392 */ 393 private final Map<String, ModelProperty> allPathPropertyInfos; 394 395 /** Map of fragment to key to column type. */ 396 private final Map<String, Map<String, ColumnType>> fragmentKeyTypes; 397 398 /** Map of fragment to keys for binary columns. */ 399 private final Map<String, List<String>> binaryFragmentKeys; 400 401 /** Maps collection table names to their type. */ 402 private final Map<String, PropertyType> collectionTables; 403 404 /** Column ordering for collections. */ 405 private final Map<String, String> collectionOrderBy; 406 407 // ------------------------------------------------------- 408 409 /** 410 * Map of schema to simple+collection fragments. Used while computing document type fragments, and for prefetch. 411 */ 412 private final Map<String, Set<String>> schemaFragments; 413 414 /** Map of doctype/complextype to simple+collection fragments. */ 415 protected final Map<String, Set<String>> typeFragments; 416 417 /** Map of mixin to simple+collection fragments. */ 418 protected final Map<String, Set<String>> mixinFragments; 419 420 /** The proxy fragments. */ 421 private final Set<String> proxyFragments; 422 423 /** Map of doctype to prefetched fragments. */ 424 protected final Map<String, Set<String>> docTypePrefetchedFragments; 425 426 /** Map of schema to child name to type. */ 427 protected final Map<String, Map<String, String>> schemaComplexChildren; 428 429 /** Map of doctype/complextype to child name to type. */ 430 protected final Map<String, Map<String, String>> typeComplexChildren; 431 432 /** Map of mixin to child name to type. */ 433 protected final Map<String, Map<String, String>> mixinComplexChildren; 434 435 /** Map of doctype to its supertype, for search. */ 436 protected final Map<String, String> documentSuperTypes; 437 438 /** Map of doctype to its subtypes (including itself), for search. */ 439 protected final Map<String, Set<String>> documentSubTypes; 440 441 /** Map of field name to fragment holding it. Used for prefetch. */ 442 protected final Map<String, String> fieldFragment; 443 444 protected final FulltextConfiguration fulltextConfiguration; 445 446 protected final Set<String> noPerDocumentQueryFacets; 447 448 /** 449 * Map of fragment -> info about whether there's a fulltext text field (PropertyType.STRING), binary field 450 * (PropertyType.BINARY), or both (PropertyType.BOOLEAN). 451 */ 452 protected final Map<String, PropertyType> fulltextInfoByFragment; 453 454 private final boolean materializeFulltextSyntheticColumn; 455 456 private final boolean supportsArrayColumns; 457 458 public Model(ModelSetup modelSetup) { 459 repositoryDescriptor = modelSetup.repositoryDescriptor; 460 materializeFulltextSyntheticColumn = modelSetup.materializeFulltextSyntheticColumn; 461 supportsArrayColumns = modelSetup.supportsArrayColumns; 462 idType = modelSetup.idType; 463 switch (idType) { 464 case STRING: 465 idPropertyType = PropertyType.STRING; 466 idCoreType = StringType.INSTANCE; 467 break; 468 case LONG: 469 idPropertyType = PropertyType.LONG; 470 idCoreType = LongType.INSTANCE; 471 break; 472 default: 473 throw new AssertionError(idType.toString()); 474 } 475 softDeleteEnabled = repositoryDescriptor.getSoftDeleteEnabled(); 476 proxiesEnabled = repositoryDescriptor.getProxiesEnabled(); 477 changeTokenEnabled = repositoryDescriptor.isChangeTokenEnabled(); 478 479 allDocTypeSchemas = new HashMap<>(); 480 mixinsDocumentTypes = new HashMap<>(); 481 documentTypesMixins = new HashMap<>(); 482 allMixinSchemas = new HashMap<>(); 483 allProxySchemas = new HashSet<>(); 484 485 fragmentPropertyInfos = new HashMap<>(); 486 schemaPropertyInfos = new HashMap<>(); 487 typePropertyInfos = new HashMap<>(); 488 mixinPropertyInfos = new HashMap<>(); 489 proxyPropertyInfos = new HashMap<>(); 490 sharedPropertyInfos = new HashMap<>(); 491 mergedPropertyInfos = new HashMap<>(); 492 schemaPathPropertyInfos = new HashMap<>(); 493 prefixToSchema = new HashMap<>(); 494 schemaSimpleTextPaths = new HashMap<>(); 495 allPathPropertyInfos = new HashMap<>(); 496 fulltextInfoByFragment = new HashMap<>(); 497 fragmentKeyTypes = new HashMap<>(); 498 binaryFragmentKeys = new HashMap<>(); 499 500 collectionTables = new HashMap<>(); 501 collectionOrderBy = new HashMap<>(); 502 503 schemaFragments = new HashMap<>(); 504 typeFragments = new HashMap<>(); 505 mixinFragments = new HashMap<>(); 506 proxyFragments = new HashSet<>(); 507 docTypePrefetchedFragments = new HashMap<>(); 508 fieldFragment = new HashMap<>(); 509 510 schemaComplexChildren = new HashMap<>(); 511 typeComplexChildren = new HashMap<>(); 512 mixinComplexChildren = new HashMap<>(); 513 514 documentSuperTypes = new HashMap<>(); 515 documentSubTypes = new HashMap<>(); 516 517 specialPropertyTypes = new HashMap<>(); 518 noPerDocumentQueryFacets = new HashSet<>(); 519 520 miscInHierarchy = false; 521 522 if (repositoryDescriptor.getFulltextDescriptor().getFulltextDisabled()) { 523 fulltextConfiguration = null; 524 } else { 525 fulltextConfiguration = new FulltextConfiguration(repositoryDescriptor.getFulltextDescriptor()); 526 } 527 528 initMainModel(); 529 initVersionsModel(); 530 if (proxiesEnabled) { 531 initProxiesModel(); 532 } 533 initLocksModel(); 534 initAclModel(); 535 initMiscModel(); 536 // models for all document types and mixins 537 initModels(); 538 if (fulltextConfiguration != null) { 539 inferFulltextInfoByFragment(); // needs mixin schemas 540 initFullTextModel(); 541 } 542 } 543 544 /** 545 * Gets the repository descriptor used for this model. 546 * 547 * @return the repository descriptor 548 */ 549 public RepositoryDescriptor getRepositoryDescriptor() { 550 return repositoryDescriptor; 551 } 552 553 /** 554 * Fixup an id that has been turned into a string for high-level Nuxeo APIs. 555 * 556 * @param id the id to fixup 557 * @return the fixed up id 558 */ 559 public Serializable idFromString(String id) { 560 switch (idType) { 561 case STRING: 562 return id; 563 case LONG: 564 // use isNumeric instead of try/catch for efficiency 565 if (StringUtils.isNumeric(id)) { 566 return Long.valueOf(id); 567 } else { 568 return NO_SUCH_LONG_ID; 569 } 570 default: 571 throw new AssertionError(idType.toString()); 572 } 573 } 574 575 /** 576 * Turns an id that may be a String or a Long into a String for high-level Nuxeo APIs. 577 * 578 * @param id the serializable id 579 * @return the string 580 */ 581 public String idToString(Serializable id) { 582 return id.toString(); 583 } 584 585 /** 586 * Records info about a system property (applying to all document types). 587 */ 588 private void addPropertyInfo(String propertyName, PropertyType propertyType, String fragmentName, 589 String fragmentKey, boolean readonly, Type coreType, ColumnType type) { 590 addPropertyInfo(null, false, propertyName, propertyType, fragmentName, fragmentKey, readonly, coreType, type); 591 } 592 593 /** 594 * Records info about one property (for a given type). Used for proxy properties. 595 */ 596 private void addPropertyInfo(String typeName, String propertyName, PropertyType propertyType, String fragmentName, 597 String fragmentKey, boolean readonly, Type coreType, ColumnType type) { 598 addPropertyInfo(typeName, false, propertyName, propertyType, fragmentName, fragmentKey, readonly, coreType, 599 type); 600 } 601 602 /** 603 * Records info about one property from a complex type or schema. 604 */ 605 private void addPropertyInfo(ComplexType complexType, String propertyName, PropertyType propertyType, 606 String fragmentName, String fragmentKey, boolean readonly, Type coreType, ColumnType type) { 607 String typeName = complexType.getName(); 608 boolean isSchema = complexType instanceof Schema; 609 addPropertyInfo(typeName, isSchema, propertyName, propertyType, fragmentName, fragmentKey, readonly, coreType, 610 type); 611 } 612 613 /** 614 * Records info about one property, in a schema-based structure and in a fragment-based structure. 615 */ 616 private void addPropertyInfo(String typeName, boolean isSchema, String propertyName, PropertyType propertyType, 617 String fragmentName, String fragmentKey, boolean readonly, Type coreType, ColumnType type) { 618 619 ModelProperty propertyInfo = new ModelProperty(propertyType, fragmentName, fragmentKey, readonly); 620 621 // per type/schema property info 622 Map<String, ModelProperty> propertyInfos; 623 if (typeName == null) { 624 propertyInfos = sharedPropertyInfos; 625 } else { 626 Map<String, Map<String, ModelProperty>> map = isSchema ? schemaPropertyInfos : typePropertyInfos; 627 propertyInfos = map.computeIfAbsent(typeName, k -> new HashMap<>()); 628 } 629 propertyInfos.put(propertyName, propertyInfo); 630 631 // merged properties 632 ModelProperty previous = mergedPropertyInfos.get(propertyName); 633 if (previous == null) { 634 mergedPropertyInfos.put(propertyName, propertyInfo); 635 } else { 636 log.debug(String.format( 637 "Schemas '%s' and '%s' both have a property '%s', " 638 + "unqualified reference in queries will use schema '%1$s'", 639 previous.fragmentName, fragmentName, propertyName)); 640 } 641 // compatibility for use of schema name as prefix 642 if (typeName != null && !propertyName.contains(":")) { 643 // allow schema name as prefix 644 propertyName = typeName + ':' + propertyName; 645 mergedPropertyInfos.putIfAbsent(propertyName, propertyInfo); 646 } 647 648 // system properties core type (no core schema available) 649 if (coreType != null) { 650 specialPropertyTypes.put(propertyName, coreType); 651 } 652 653 // per-fragment property info 654 Map<String, ModelProperty> fragmentKeyInfos = fragmentPropertyInfos.computeIfAbsent(fragmentName, 655 k -> new HashMap<>()); 656 if (fragmentKey != null) { 657 fragmentKeyInfos.put(fragmentKey, propertyInfo); 658 } 659 660 // per-fragment keys type 661 if (fragmentKey != null && type != null) { 662 fragmentKeyTypes.computeIfAbsent(fragmentName, k -> new LinkedHashMap<>()).put(fragmentKey, type); 663 664 // record binary columns for the GC 665 if (type.spec == ColumnSpec.BLOBID) { 666 binaryFragmentKeys.computeIfAbsent(fragmentName, k -> new ArrayList<>(1)).add(fragmentKey); 667 } 668 } // else collection, uses addCollectionFragmentInfos directly 669 } 670 671 private void addCollectionFragmentInfos(String fragmentName, PropertyType propertyType, String orderBy, 672 Map<String, ColumnType> keysType) { 673 collectionTables.put(fragmentName, propertyType); 674 collectionOrderBy.put(fragmentName, orderBy); 675 fragmentKeyTypes.put(fragmentName, keysType); 676 } 677 678 // fill the map for key with property infos for the given schemas 679 // different maps are used for doctypes or mixins 680 private void inferPropertyInfos(Map<String, Map<String, ModelProperty>> map, String key, Set<String> schemaNames) { 681 Map<String, ModelProperty> propertyInfos = map.computeIfAbsent(key, k -> new HashMap<>()); 682 for (String schemaName : schemaNames) { 683 Map<String, ModelProperty> infos = schemaPropertyInfos.get(schemaName); 684 if (infos != null) { 685 propertyInfos.putAll(infos); 686 } 687 // else schema with no properties (complex list) 688 } 689 } 690 691 /** 692 * Infers all possible paths for properties in a schema. 693 */ 694 private void inferSchemaPropertyPaths(Schema schema) { 695 String schemaName = schema.getName(); 696 if (schemaPathPropertyInfos.containsKey(schemaName)) { 697 return; 698 } 699 Map<String, ModelProperty> propertyInfoByPath = new HashMap<>(); 700 inferTypePropertyPaths(schema, "", propertyInfoByPath, null); 701 schemaPathPropertyInfos.put(schemaName, propertyInfoByPath); 702 // allow schema-as-prefix if schemas has no prefix, if non-complex 703 Map<String, ModelProperty> alsoWithPrefixes = new HashMap<>(propertyInfoByPath); 704 String prefix = schema.getNamespace().prefix; 705 if (prefix.isEmpty()) { 706 for (Entry<String, ModelProperty> e : propertyInfoByPath.entrySet()) { 707 alsoWithPrefixes.put(schemaName + ':' + e.getKey(), e.getValue()); 708 } 709 } else { 710 prefixToSchema.put(prefix, schemaName); 711 } 712 allPathPropertyInfos.putAll(alsoWithPrefixes); 713 // those for simpletext properties 714 Set<String> simplePaths = new HashSet<>(); 715 for (Entry<String, ModelProperty> entry : propertyInfoByPath.entrySet()) { 716 ModelProperty pi = entry.getValue(); 717 if (pi.isIntermediateSegment()) { 718 continue; 719 } 720 if (pi.propertyType != PropertyType.STRING && pi.propertyType != PropertyType.ARRAY_STRING) { 721 continue; 722 } 723 simplePaths.add(entry.getKey()); 724 } 725 schemaSimpleTextPaths.put(schemaName, simplePaths); 726 } 727 728 // recurses in a schema or complex type 729 private void inferTypePropertyPaths(ComplexType complexType, String prefix, 730 Map<String, ModelProperty> propertyInfoByPath, Set<String> done) { 731 if (done == null) { 732 done = new LinkedHashSet<>(); 733 } 734 String typeName = complexType.getName(); 735 if (done.contains(typeName)) { 736 log.warn("Complex type " + typeName + " refers to itself recursively: " + done); 737 // stop recursion 738 return; 739 } 740 done.add(typeName); 741 742 for (Field field : complexType.getFields()) { 743 String propertyName = field.getName().getPrefixedName(); 744 String path = prefix + propertyName; 745 Type fieldType = field.getType(); 746 if (fieldType.isComplexType()) { 747 // complex type 748 propertyInfoByPath.put(path, new ModelProperty(propertyName)); 749 inferTypePropertyPaths((ComplexType) fieldType, path + '/', propertyInfoByPath, done); 750 continue; 751 } else if (fieldType.isListType()) { 752 Type listFieldType = ((ListType) fieldType).getFieldType(); 753 if (!listFieldType.isSimpleType()) { 754 // complex list 755 propertyInfoByPath.put(path + "/*", new ModelProperty(propertyName)); 756 inferTypePropertyPaths((ComplexType) listFieldType, path + "/*/", propertyInfoByPath, done); 757 continue; 758 } 759 // else array 760 } 761 // else primitive type 762 // in both cases, record it 763 Map<String, Map<String, ModelProperty>> map = (complexType instanceof Schema) ? schemaPropertyInfos 764 : typePropertyInfos; 765 ModelProperty pi = map.get(typeName).get(propertyName); 766 propertyInfoByPath.put(path, pi); 767 // also add the propname/* path for array elements 768 if (pi.propertyType.isArray()) { 769 propertyInfoByPath.put(path + "/*", pi); 770 if (!supportsArrayColumns) { 771 // pseudo-syntax with ending "#" to get to the pos column 772 String posPropertyName = propertyName + "#"; 773 ModelProperty posPi = map.get(typeName).get(posPropertyName); 774 propertyInfoByPath.put(path + "#", posPi); 775 } 776 } 777 } 778 done.remove(typeName); 779 } 780 781 private void inferFulltextInfoByFragment() { 782 // simple fragments 783 for (Entry<String, Map<String, ModelProperty>> es : fragmentPropertyInfos.entrySet()) { 784 String fragmentName = es.getKey(); 785 Map<String, ModelProperty> infos = es.getValue(); 786 if (infos == null) { 787 continue; 788 } 789 PropertyType type = null; 790 for (ModelProperty info : infos.values()) { 791 if (info != null && info.fulltext) { 792 PropertyType t = info.propertyType; 793 if (t == PropertyType.STRING || t == PropertyType.BINARY) { 794 if (type == null) { 795 type = t; 796 continue; 797 } 798 if (type != t) { 799 type = PropertyType.BOOLEAN; // both 800 break; 801 } 802 } 803 } 804 } 805 fulltextInfoByFragment.put(fragmentName, type); 806 } 807 // collection fragments 808 for (Entry<String, PropertyType> es : collectionTables.entrySet()) { 809 String fragmentName = es.getKey(); 810 PropertyType type = es.getValue(); 811 if (type == PropertyType.ARRAY_STRING || type == PropertyType.ARRAY_BINARY) { 812 fulltextInfoByFragment.put(fragmentName, type.getArrayBaseType()); 813 } 814 } 815 } 816 817 /** Get doctype/complextype property info. */ 818 public ModelProperty getPropertyInfo(String typeName, String propertyName) { 819 Map<String, ModelProperty> propertyInfos = typePropertyInfos.get(typeName); 820 if (propertyInfos == null) { 821 // no such doctype/complextype 822 return null; 823 } 824 ModelProperty propertyInfo = propertyInfos.get(propertyName); 825 return propertyInfo != null ? propertyInfo : sharedPropertyInfos.get(propertyName); 826 } 827 828 public Map<String, ModelProperty> getMixinPropertyInfos(String mixin) { 829 return mixinPropertyInfos.get(mixin); 830 } 831 832 // for all types for now 833 public ModelProperty getProxySchemasPropertyInfo(String propertyName) { 834 ModelProperty propertyInfo = proxyPropertyInfos.get(propertyName); 835 return propertyInfo != null ? propertyInfo : sharedPropertyInfos.get(propertyName); 836 } 837 838 public ModelProperty getMixinPropertyInfo(String mixin, String propertyName) { 839 Map<String, ModelProperty> propertyInfos = mixinPropertyInfos.get(mixin); 840 if (propertyInfos == null) { 841 // no such mixin 842 return null; 843 } 844 return propertyInfos.get(propertyName); 845 } 846 847 public ModelProperty getPropertyInfo(String propertyName) { 848 return mergedPropertyInfos.get(propertyName); 849 } 850 851 /** 852 * Gets the model of the property for the given path. Returns something with 853 * {@link ModelProperty#isIntermediateSegment()} = true for an intermediate segment of a complex property. 854 */ 855 public ModelProperty getPathPropertyInfo(String xpath) { 856 return allPathPropertyInfos.get(xpath); 857 } 858 859 public Set<String> getPropertyInfoNames() { 860 return mergedPropertyInfos.keySet(); 861 } 862 863 public ModelProperty getPathPropertyInfo(String primaryType, String[] mixinTypes, String path) { 864 for (String schema : getAllSchemas(primaryType, mixinTypes)) { 865 Map<String, ModelProperty> propertyInfoByPath = schemaPathPropertyInfos.get(schema); 866 if (propertyInfoByPath != null) { 867 ModelProperty pi = propertyInfoByPath.get(path); 868 if (pi != null) { 869 return pi; 870 } 871 } 872 } 873 return null; 874 } 875 876 public Map<String, String> getTypeComplexChildren(String typeName) { 877 return typeComplexChildren.get(typeName); 878 } 879 880 public Map<String, String> getMixinComplexChildren(String mixin) { 881 return mixinComplexChildren.get(mixin); 882 } 883 884 public Set<String> getSimpleTextPropertyPaths(String primaryType, String[] mixinTypes) { 885 Set<String> paths = new HashSet<>(); 886 for (String schema : getAllSchemas(primaryType, mixinTypes)) { 887 Set<String> p = schemaSimpleTextPaths.get(schema); 888 if (p != null) { 889 paths.addAll(p); 890 } 891 } 892 return paths; 893 } 894 895 /** 896 * Checks if the given xpath, when resolved on a proxy, points to a proxy-specific schema instead of the target 897 * document. 898 */ 899 public boolean isProxySchemaPath(String xpath) { 900 int p = xpath.indexOf(':'); 901 if (p == -1) { 902 return false; // no schema/prefix -> not on proxy 903 } 904 String prefix = xpath.substring(0, p); 905 String schema = prefixToSchema.get(prefix); 906 if (schema == null) { 907 schema = prefix; 908 } 909 return allProxySchemas.contains(schema); 910 } 911 912 private Set<String> getAllSchemas(String primaryType, String[] mixinTypes) { 913 Set<String> schemas = new LinkedHashSet<>(); 914 Set<String> s = allDocTypeSchemas.get(primaryType); 915 if (s != null) { 916 schemas.addAll(s); 917 } 918 for (String mixin : mixinTypes) { 919 s = allMixinSchemas.get(mixin); 920 if (s != null) { 921 schemas.addAll(s); 922 } 923 } 924 return schemas; 925 } 926 927 public FulltextConfiguration getFulltextConfiguration() { 928 return fulltextConfiguration; 929 } 930 931 /** 932 * Finds out if a field is to be indexed as fulltext. 933 * 934 * @param fragmentKey the key or {@code null} for a collection 935 * @return {@link PropertyType#STRING} or {@link PropertyType#BINARY} if this field is to be indexed as fulltext 936 */ 937 public PropertyType getFulltextFieldType(String fragmentName, String fragmentKey) { 938 if (fragmentKey == null) { 939 PropertyType type = collectionTables.get(fragmentName); 940 if (type == PropertyType.ARRAY_STRING || type == PropertyType.ARRAY_BINARY) { 941 return type.getArrayBaseType(); 942 } 943 return null; 944 } else { 945 Map<String, ModelProperty> infos = fragmentPropertyInfos.get(fragmentName); 946 if (infos == null) { 947 return null; 948 } 949 ModelProperty info = infos.get(fragmentKey); 950 if (info != null && info.fulltext) { 951 return info.propertyType; 952 } 953 return null; 954 } 955 } 956 957 /** 958 * Checks if a fragment has any field indexable as fulltext. 959 * 960 * @return PropertyType.STRING, PropertyType.BINARY, or PropertyType.BOOLEAN for both. 961 */ 962 public PropertyType getFulltextInfoForFragment(String fragmentName) { 963 return fulltextInfoByFragment.get(fragmentName); 964 } 965 966 public Type getSpecialPropertyType(String propertyName) { 967 return specialPropertyTypes.get(propertyName); 968 } 969 970 public PropertyType getCollectionFragmentType(String fragmentName) { 971 return collectionTables.get(fragmentName); 972 } 973 974 public boolean isCollectionFragment(String fragmentName) { 975 return collectionTables.containsKey(fragmentName); 976 } 977 978 public String getCollectionOrderBy(String fragmentName) { 979 return collectionOrderBy.get(fragmentName); 980 } 981 982 public Set<String> getFragmentNames() { 983 return fragmentKeyTypes.keySet(); 984 } 985 986 public Map<String, ColumnType> getFragmentKeysType(String fragmentName) { 987 return fragmentKeyTypes.get(fragmentName); 988 } 989 990 public Map<String, List<String>> getBinaryPropertyInfos() { 991 return binaryFragmentKeys; 992 } 993 994 private void addTypeFragments(String typeName, Set<String> fragmentNames) { 995 typeFragments.put(typeName, fragmentNames); 996 } 997 998 private void addFieldFragment(Field field, String fragmentName) { 999 String fieldName = field.getName().toString(); 1000 fieldFragment.put(fieldName, fragmentName); 1001 } 1002 1003 private void addDocTypePrefetchedFragments(String docTypeName, Set<String> fragmentNames) { 1004 docTypePrefetchedFragments.computeIfAbsent(docTypeName, k -> new HashSet<>()).addAll(fragmentNames); 1005 } 1006 1007 /** 1008 * Gets the fragments for a type (doctype or complex type). 1009 */ 1010 private Set<String> getTypeFragments(String docTypeName) { 1011 return typeFragments.get(docTypeName); 1012 } 1013 1014 /** 1015 * Gets the fragments for a mixin. 1016 */ 1017 private Set<String> getMixinFragments(String mixin) { 1018 return mixinFragments.get(mixin); 1019 } 1020 1021 public Set<String> getTypePrefetchedFragments(String typeName) { 1022 return docTypePrefetchedFragments.get(typeName); 1023 } 1024 1025 /** 1026 * Checks if we have a type (doctype or complex type). 1027 */ 1028 public boolean isType(String typeName) { 1029 return typeFragments.containsKey(typeName); 1030 } 1031 1032 public String getDocumentSuperType(String typeName) { 1033 return documentSuperTypes.get(typeName); 1034 } 1035 1036 public Set<String> getDocumentSubTypes(String typeName) { 1037 return documentSubTypes.get(typeName); 1038 } 1039 1040 public Set<String> getDocumentTypes() { 1041 return documentTypesMixins.keySet(); 1042 } 1043 1044 public Set<String> getDocumentTypeFacets(String typeName) { 1045 Set<String> facets = documentTypesMixins.get(typeName); 1046 return facets == null ? Collections.<String> emptySet() : facets; 1047 } 1048 1049 public Set<String> getMixinDocumentTypes(String mixin) { 1050 Set<String> types = mixinsDocumentTypes.get(mixin); 1051 return types == null ? Collections.<String> emptySet() : types; 1052 } 1053 1054 /** 1055 * Given a map of id to types, returns a map of fragment names to ids. 1056 */ 1057 public Map<String, Set<Serializable>> getPerFragmentIds(Map<Serializable, IdWithTypes> idToTypes) { 1058 Map<String, Set<Serializable>> allFragmentIds = new HashMap<>(); 1059 for (Entry<Serializable, IdWithTypes> e : idToTypes.entrySet()) { 1060 Serializable id = e.getKey(); 1061 IdWithTypes typeInfo = e.getValue(); 1062 for (String fragmentName : getTypeFragments(typeInfo)) { 1063 allFragmentIds.computeIfAbsent(fragmentName, k -> new HashSet<>()).add(id); 1064 } 1065 } 1066 return allFragmentIds; 1067 } 1068 1069 /** 1070 * Gets the type fragments for a primary type and mixin types. Hierarchy is included. 1071 */ 1072 public Set<String> getTypeFragments(IdWithTypes typeInfo) { 1073 Set<String> fragmentNames = new HashSet<>(); 1074 fragmentNames.add(HIER_TABLE_NAME); 1075 Set<String> tf = getTypeFragments(typeInfo.primaryType); 1076 if (tf != null) { 1077 // null if unknown type left in the database 1078 fragmentNames.addAll(tf); 1079 } 1080 String[] mixins = typeInfo.mixinTypes; 1081 if (mixins != null) { 1082 for (String mixin : mixins) { 1083 Set<String> mf = getMixinFragments(mixin); 1084 if (mf != null) { 1085 fragmentNames.addAll(mf); 1086 } 1087 } 1088 } 1089 if (PROXY_TYPE.equals(typeInfo.primaryType)) { 1090 fragmentNames.addAll(proxyFragments); 1091 } 1092 return fragmentNames; 1093 } 1094 1095 public Set<String> getNoPerDocumentQueryFacets() { 1096 return noPerDocumentQueryFacets; 1097 } 1098 1099 /** 1100 * Creates all the models. 1101 */ 1102 private void initModels() { 1103 SchemaManager schemaManager = Framework.getService(SchemaManager.class); 1104 log.debug("Schemas fields from descriptor: " + repositoryDescriptor.schemaFields); 1105 // document types 1106 for (DocumentType docType : schemaManager.getDocumentTypes()) { 1107 initDocTypeOrMixinModel(docType.getName(), docType.getSchemas(), allDocTypeSchemas, typeFragments, 1108 typePropertyInfos, typeComplexChildren, true); 1109 initDocTypePrefetch(docType); 1110 initDocTypeMixins(docType); 1111 inferSuperType(docType); 1112 } 1113 // mixins 1114 for (CompositeType type : schemaManager.getFacets()) { 1115 initDocTypeOrMixinModel(type.getName(), type.getSchemas(), allMixinSchemas, mixinFragments, 1116 mixinPropertyInfos, mixinComplexChildren, false); 1117 log.debug("Fragments for facet " + type.getName() + ": " + getMixinFragments(type.getName())); 1118 } 1119 // proxy schemas 1120 initProxySchemas(schemaManager.getProxySchemas(null)); 1121 // second pass to get subtypes (needs all supertypes) 1122 for (DocumentType documentType : schemaManager.getDocumentTypes()) { 1123 inferSubTypes(documentType); 1124 } 1125 // init no per instance query facets 1126 initNoPerDocumentQueryFacets(schemaManager); 1127 } 1128 1129 private void initProxySchemas(List<Schema> proxySchemas) { 1130 Map<String, Set<String>> allSchemas = new HashMap<>(); 1131 Map<String, Set<String>> allFragments = new HashMap<>(); 1132 Map<String, Map<String, String>> allChildren = new HashMap<>(); 1133 Map<String, Map<String, ModelProperty>> allPropertyInfos = new HashMap<>(); 1134 String key = "__proxies__"; // not stored 1135 initDocTypeOrMixinModel(key, proxySchemas, allSchemas, allFragments, allPropertyInfos, allChildren, false); 1136 allProxySchemas.addAll(allSchemas.get(key)); 1137 proxyFragments.addAll(allFragments.get(key)); 1138 proxyPropertyInfos.putAll(allPropertyInfos.get(key)); 1139 typeComplexChildren.put(PROXY_TYPE, allChildren.get(key)); 1140 } 1141 1142 private Set<String> getCommonFragments(String typeName) { 1143 Set<String> fragments = new HashSet<>(5); 1144 fragments.add(VERSION_TABLE_NAME); 1145 fragments.add(ACL_TABLE_NAME); 1146 if (!miscInHierarchy) { 1147 fragments.add(MISC_TABLE_NAME); 1148 } 1149 if (fulltextConfiguration != null && fulltextConfiguration.isFulltextIndexable(typeName)) { 1150 fragments.add(FULLTEXT_TABLE_NAME); 1151 } 1152 return fragments; 1153 } 1154 1155 private Set<String> getCommonFragmentsPrefetched() { 1156 Set<String> fragments = new HashSet<>(5); 1157 fragments.add(VERSION_TABLE_NAME); 1158 fragments.add(ACL_TABLE_NAME); 1159 if (!miscInHierarchy) { 1160 fragments.add(MISC_TABLE_NAME); 1161 } 1162 return fragments; 1163 } 1164 1165 /** 1166 * For a doctype or mixin type, init the schemas-related structures. 1167 */ 1168 private void initDocTypeOrMixinModel(String typeName, Collection<Schema> schemas, 1169 Map<String, Set<String>> schemasMap, Map<String, Set<String>> fragmentsMap, 1170 Map<String, Map<String, ModelProperty>> propertyInfoMap, 1171 Map<String, Map<String, String>> complexChildrenMap, boolean addCommonFragments) { 1172 Set<String> schemaNames = new HashSet<>(); 1173 Set<String> fragmentNames = new HashSet<>(); 1174 Map<String, String> complexChildren = new HashMap<>(); 1175 if (addCommonFragments) { 1176 fragmentNames.addAll(getCommonFragments(typeName)); 1177 } 1178 for (Schema schema : schemas) { 1179 if (schema == null) { 1180 // happens when a type refers to a nonexistent schema 1181 // TODO log and avoid nulls earlier 1182 continue; 1183 } 1184 schemaNames.add(schema.getName()); 1185 try { 1186 fragmentNames.addAll(initSchemaModel(schema)); 1187 } catch (NuxeoException e) { 1188 e.addInfo(String.format("Error initializing schema '%s' for composite type '%s'", schema.getName(), 1189 typeName)); 1190 throw e; 1191 } 1192 inferSchemaPropertyPaths(schema); 1193 complexChildren.putAll(schemaComplexChildren.get(schema.getName())); 1194 } 1195 schemasMap.put(typeName, schemaNames); 1196 fragmentsMap.put(typeName, fragmentNames); 1197 complexChildrenMap.put(typeName, complexChildren); 1198 inferPropertyInfos(propertyInfoMap, typeName, schemaNames); 1199 } 1200 1201 private void initDocTypePrefetch(DocumentType docType) { 1202 String docTypeName = docType.getName(); 1203 PrefetchInfo prefetch = docType.getPrefetchInfo(); 1204 if (prefetch != null) { 1205 Set<String> documentTypeFragments = getTypeFragments(docTypeName); 1206 for (String fieldName : prefetch.getFields()) { 1207 // prefetch all the relevant fragments 1208 // TODO deal with full xpath 1209 String fragment = fieldFragment.get(fieldName); 1210 if (fragment != null) { 1211 // checks that the field actually belongs 1212 // to the type 1213 if (documentTypeFragments.contains(fragment)) { 1214 addDocTypePrefetchedFragments(docTypeName, Collections.singleton(fragment)); 1215 } 1216 } 1217 } 1218 for (String schemaName : prefetch.getSchemas()) { 1219 Set<String> fragments = schemaFragments.get(schemaName); 1220 if (fragments != null) { 1221 addDocTypePrefetchedFragments(docTypeName, fragments); 1222 } 1223 } 1224 } 1225 // always prefetch ACLs, versions, misc (for lifecycle) 1226 addDocTypePrefetchedFragments(docTypeName, getCommonFragmentsPrefetched()); 1227 1228 log.debug("Fragments for type " + docTypeName + ": " + getTypeFragments(docTypeName) + ", prefetch: " 1229 + getTypePrefetchedFragments(docTypeName)); 1230 } 1231 1232 private void initDocTypeMixins(DocumentType docType) { 1233 String docTypeName = docType.getName(); 1234 Set<String> mixins = docType.getFacets(); 1235 documentTypesMixins.put(docTypeName, new HashSet<>(mixins)); 1236 for (String mixin : mixins) { 1237 mixinsDocumentTypes.computeIfAbsent(mixin, k -> new HashSet<>()).add(docTypeName); 1238 } 1239 } 1240 1241 private void inferSuperType(DocumentType docType) { 1242 Type superType = docType.getSuperType(); 1243 if (superType != null) { 1244 documentSuperTypes.put(docType.getName(), superType.getName()); 1245 } 1246 } 1247 1248 private void inferSubTypes(DocumentType docType) { 1249 String type = docType.getName(); 1250 String superType = type; 1251 do { 1252 documentSubTypes.computeIfAbsent(superType, k -> new HashSet<>()).add(type); 1253 superType = documentSuperTypes.get(superType); 1254 } while (superType != null); 1255 } 1256 1257 private void initNoPerDocumentQueryFacets(SchemaManager schemaManager) { 1258 noPerDocumentQueryFacets.addAll(schemaManager.getNoPerDocumentQueryFacets()); 1259 } 1260 1261 /** 1262 * Special model for the main table (the one containing the primary type information). 1263 * <p> 1264 * If the main table is not separate from the hierarchy table, then it's will not really be instantiated by itself 1265 * but merged into the hierarchy table. 1266 */ 1267 private void initMainModel() { 1268 addPropertyInfo(MAIN_PRIMARY_TYPE_PROP, PropertyType.STRING, HIER_TABLE_NAME, MAIN_PRIMARY_TYPE_KEY, true, null, 1269 ColumnType.SYSNAME); 1270 addPropertyInfo(MAIN_MIXIN_TYPES_PROP, PropertyType.STRING, HIER_TABLE_NAME, MAIN_MIXIN_TYPES_KEY, false, null, 1271 ColumnType.SYSNAMEARRAY); 1272 addPropertyInfo(MAIN_CHECKED_IN_PROP, PropertyType.BOOLEAN, HIER_TABLE_NAME, MAIN_CHECKED_IN_KEY, false, 1273 BooleanType.INSTANCE, ColumnType.BOOLEAN); 1274 addPropertyInfo(MAIN_BASE_VERSION_PROP, idPropertyType, HIER_TABLE_NAME, MAIN_BASE_VERSION_KEY, false, 1275 idCoreType, ColumnType.NODEVAL); 1276 addPropertyInfo(MAIN_MAJOR_VERSION_PROP, PropertyType.LONG, HIER_TABLE_NAME, MAIN_MAJOR_VERSION_KEY, false, 1277 LongType.INSTANCE, ColumnType.INTEGER); 1278 addPropertyInfo(MAIN_MINOR_VERSION_PROP, PropertyType.LONG, HIER_TABLE_NAME, MAIN_MINOR_VERSION_KEY, false, 1279 LongType.INSTANCE, ColumnType.INTEGER); 1280 addPropertyInfo(MAIN_IS_VERSION_PROP, PropertyType.BOOLEAN, HIER_TABLE_NAME, MAIN_IS_VERSION_KEY, false, 1281 BooleanType.INSTANCE, ColumnType.BOOLEAN); 1282 addPropertyInfo(MAIN_IS_RETENTION_ACTIVE_PROP, PropertyType.BOOLEAN, HIER_TABLE_NAME, 1283 MAIN_IS_RETENTION_ACTIVE_KEY, false, BooleanType.INSTANCE, ColumnType.BOOLEAN); 1284 addPropertyInfo(MAIN_IS_TRASHED_PROP, PropertyType.BOOLEAN, HIER_TABLE_NAME, MAIN_IS_TRASHED_KEY, false, 1285 BooleanType.INSTANCE, ColumnType.BOOLEAN); 1286 if (changeTokenEnabled) { 1287 addPropertyInfo(MAIN_SYS_CHANGE_TOKEN_PROP, PropertyType.LONG, HIER_TABLE_NAME, MAIN_SYS_CHANGE_TOKEN_KEY, 1288 false, LongType.INSTANCE, ColumnType.LONG); 1289 addPropertyInfo(MAIN_CHANGE_TOKEN_PROP, PropertyType.LONG, HIER_TABLE_NAME, MAIN_CHANGE_TOKEN_KEY, false, 1290 LongType.INSTANCE, ColumnType.LONG); 1291 } 1292 if (softDeleteEnabled) { 1293 addPropertyInfo(MAIN_IS_DELETED_PROP, PropertyType.BOOLEAN, HIER_TABLE_NAME, MAIN_IS_DELETED_KEY, true, 1294 BooleanType.INSTANCE, ColumnType.BOOLEAN); 1295 addPropertyInfo(MAIN_DELETED_TIME_PROP, PropertyType.DATETIME, HIER_TABLE_NAME, MAIN_DELETED_TIME_KEY, true, 1296 DateType.INSTANCE, ColumnType.TIMESTAMP); 1297 } 1298 } 1299 1300 /** 1301 * Special model for the "misc" table (lifecycle, dirty.). 1302 */ 1303 private void initMiscModel() { 1304 String fragmentName = miscInHierarchy ? HIER_TABLE_NAME : MISC_TABLE_NAME; 1305 addPropertyInfo(MISC_LIFECYCLE_POLICY_PROP, PropertyType.STRING, fragmentName, MISC_LIFECYCLE_POLICY_KEY, false, 1306 StringType.INSTANCE, ColumnType.SYSNAME); 1307 addPropertyInfo(MISC_LIFECYCLE_STATE_PROP, PropertyType.STRING, fragmentName, MISC_LIFECYCLE_STATE_KEY, false, 1308 StringType.INSTANCE, ColumnType.SYSNAME); 1309 } 1310 1311 /** 1312 * Special model for the versions table. 1313 */ 1314 private void initVersionsModel() { 1315 addPropertyInfo(VERSION_VERSIONABLE_PROP, idPropertyType, VERSION_TABLE_NAME, VERSION_VERSIONABLE_KEY, false, 1316 idCoreType, ColumnType.NODEVAL); 1317 addPropertyInfo(VERSION_CREATED_PROP, PropertyType.DATETIME, VERSION_TABLE_NAME, VERSION_CREATED_KEY, false, 1318 DateType.INSTANCE, ColumnType.TIMESTAMP); 1319 addPropertyInfo(VERSION_LABEL_PROP, PropertyType.STRING, VERSION_TABLE_NAME, VERSION_LABEL_KEY, false, 1320 StringType.INSTANCE, ColumnType.SYSNAME); 1321 addPropertyInfo(VERSION_DESCRIPTION_PROP, PropertyType.STRING, VERSION_TABLE_NAME, VERSION_DESCRIPTION_KEY, 1322 false, StringType.INSTANCE, ColumnType.STRING); 1323 addPropertyInfo(VERSION_IS_LATEST_PROP, PropertyType.BOOLEAN, VERSION_TABLE_NAME, VERSION_IS_LATEST_KEY, false, 1324 BooleanType.INSTANCE, ColumnType.BOOLEAN); 1325 addPropertyInfo(VERSION_IS_LATEST_MAJOR_PROP, PropertyType.BOOLEAN, VERSION_TABLE_NAME, 1326 VERSION_IS_LATEST_MAJOR_KEY, false, BooleanType.INSTANCE, ColumnType.BOOLEAN); 1327 } 1328 1329 /** 1330 * Special model for the proxies table. 1331 */ 1332 private void initProxiesModel() { 1333 String type = PROXY_TYPE; 1334 addPropertyInfo(type, PROXY_TARGET_PROP, idPropertyType, PROXY_TABLE_NAME, PROXY_TARGET_KEY, false, idCoreType, 1335 ColumnType.NODEIDFKNP); 1336 addPropertyInfo(type, PROXY_VERSIONABLE_PROP, idPropertyType, PROXY_TABLE_NAME, PROXY_VERSIONABLE_KEY, false, 1337 idCoreType, ColumnType.NODEVAL); 1338 addTypeFragments(type, Collections.singleton(PROXY_TABLE_NAME)); 1339 } 1340 1341 /** 1342 * Special model for the locks table (also, primary key has no foreign key, see {@link SQLInfo#initFragmentSQL}. 1343 */ 1344 private void initLocksModel() { 1345 addPropertyInfo(LOCK_OWNER_PROP, PropertyType.STRING, LOCK_TABLE_NAME, LOCK_OWNER_KEY, false, 1346 StringType.INSTANCE, ColumnType.SYSNAME); 1347 addPropertyInfo(LOCK_CREATED_PROP, PropertyType.DATETIME, LOCK_TABLE_NAME, LOCK_CREATED_KEY, false, 1348 DateType.INSTANCE, ColumnType.TIMESTAMP); 1349 } 1350 1351 /** 1352 * Special model for the fulltext table. 1353 */ 1354 private void initFullTextModel() { 1355 addPropertyInfo(FULLTEXT_JOBID_PROP, PropertyType.STRING, FULLTEXT_TABLE_NAME, FULLTEXT_JOBID_KEY, false, 1356 StringType.INSTANCE, ColumnType.SYSNAME); 1357 for (String indexName : fulltextConfiguration.indexNames) { 1358 String suffix = getFulltextIndexSuffix(indexName); 1359 if (materializeFulltextSyntheticColumn) { 1360 addPropertyInfo(FULLTEXT_FULLTEXT_PROP + suffix, PropertyType.STRING, FULLTEXT_TABLE_NAME, 1361 FULLTEXT_FULLTEXT_KEY + suffix, false, StringType.INSTANCE, ColumnType.FTINDEXED); 1362 } 1363 addPropertyInfo(FULLTEXT_SIMPLETEXT_PROP + suffix, PropertyType.STRING, FULLTEXT_TABLE_NAME, 1364 FULLTEXT_SIMPLETEXT_KEY + suffix, false, StringType.INSTANCE, ColumnType.FTSTORED); 1365 addPropertyInfo(FULLTEXT_BINARYTEXT_PROP + suffix, PropertyType.STRING, FULLTEXT_TABLE_NAME, 1366 FULLTEXT_BINARYTEXT_KEY + suffix, false, StringType.INSTANCE, ColumnType.FTSTORED); 1367 } 1368 } 1369 1370 public String getFulltextIndexSuffix(String indexName) { 1371 return indexName.equals(FULLTEXT_DEFAULT_INDEX) ? "" : '_' + indexName; 1372 } 1373 1374 /** 1375 * Special collection-like model for the ACL table. 1376 */ 1377 private void initAclModel() { 1378 Map<String, ColumnType> keysType = new LinkedHashMap<>(); 1379 keysType.put(ACL_POS_KEY, ColumnType.INTEGER); 1380 keysType.put(ACL_NAME_KEY, ColumnType.SYSNAME); 1381 keysType.put(ACL_GRANT_KEY, ColumnType.BOOLEAN); 1382 keysType.put(ACL_PERMISSION_KEY, ColumnType.SYSNAME); 1383 keysType.put(ACL_CREATOR_KEY, ColumnType.SYSNAME); 1384 keysType.put(ACL_BEGIN_KEY, ColumnType.TIMESTAMP); 1385 keysType.put(ACL_END_KEY, ColumnType.TIMESTAMP); 1386 keysType.put(ACL_STATUS_KEY, ColumnType.LONG); 1387 keysType.put(ACL_USER_KEY, ColumnType.SYSNAME); 1388 keysType.put(ACL_GROUP_KEY, ColumnType.SYSNAME); 1389 String fragmentName = ACL_TABLE_NAME; 1390 addCollectionFragmentInfos(fragmentName, PropertyType.COLL_ACL, ACL_POS_KEY, keysType); 1391 addPropertyInfo(ACL_PROP, PropertyType.COLL_ACL, fragmentName, null, false, null, null); 1392 // for query 1393 // composed of NXQL.ECM_ACL and NXQL.ECM_ACL_PRINCIPAL etc. 1394 allPathPropertyInfos.put("ecm:acl.principal/*", 1395 new ModelProperty(PropertyType.STRING, fragmentName, ACL_USER_KEY, true)); 1396 allPathPropertyInfos.put("ecm:acl.permission/*", 1397 new ModelProperty(PropertyType.STRING, fragmentName, ACL_PERMISSION_KEY, true)); 1398 allPathPropertyInfos.put("ecm:acl.grant/*", 1399 new ModelProperty(PropertyType.BOOLEAN, fragmentName, ACL_GRANT_KEY, true)); 1400 allPathPropertyInfos.put("ecm:acl.name/*", 1401 new ModelProperty(PropertyType.STRING, fragmentName, ACL_NAME_KEY, true)); 1402 allPathPropertyInfos.put("ecm:acl.pos/*", 1403 new ModelProperty(PropertyType.LONG, fragmentName, ACL_POS_KEY, true)); 1404 allPathPropertyInfos.put("ecm:acl.creator/*", 1405 new ModelProperty(PropertyType.STRING, fragmentName, ACL_CREATOR_KEY, true)); 1406 allPathPropertyInfos.put("ecm:acl.begin/*", 1407 new ModelProperty(PropertyType.DATETIME, fragmentName, ACL_BEGIN_KEY, true)); 1408 allPathPropertyInfos.put("ecm:acl.end/*", 1409 new ModelProperty(PropertyType.DATETIME, fragmentName, ACL_END_KEY, true)); 1410 allPathPropertyInfos.put("ecm:acl.status/*", 1411 new ModelProperty(PropertyType.LONG, fragmentName, ACL_STATUS_KEY, true)); 1412 } 1413 1414 /** 1415 * Creates the model for a schema. 1416 */ 1417 private Set<String> initSchemaModel(Schema schema) { 1418 initComplexTypeModel(schema); 1419 return schemaFragments.get(schema.getName()); 1420 } 1421 1422 /** 1423 * Creates the model for a complex type or a schema. Recurses in complex types. 1424 * <p> 1425 * Adds the simple+collection fragments to {@link #typeFragments} or {@link #schemaFragments}. 1426 */ 1427 private void initComplexTypeModel(ComplexType complexType) { 1428 String typeName = complexType.getName(); 1429 boolean isSchema = complexType instanceof Schema; 1430 if (isSchema && schemaFragments.containsKey(typeName)) { 1431 return; 1432 } else if (!isSchema && typeFragments.containsKey(typeName)) { 1433 return; 1434 } 1435 /** The fragment names to use for this type, usually just one. */ 1436 Set<String> fragmentNames = new HashSet<>(1); 1437 /** The children complex properties for this type. */ 1438 Map<String, String> complexChildren = new HashMap<>(1); 1439 1440 log.debug("Making model for type " + typeName); 1441 1442 /** Initialized if this type has a table associated. */ 1443 for (Field field : complexType.getFields()) { 1444 Type fieldType = field.getType(); 1445 if (fieldType.isComplexType()) { 1446 /* 1447 * Complex type. 1448 */ 1449 String propertyName = field.getName().getPrefixedName(); 1450 complexChildren.put(propertyName, fieldType.getName()); 1451 initComplexTypeModel((ComplexType) fieldType); 1452 } else { 1453 String propertyName = field.getName().getPrefixedName(); 1454 FieldDescriptor fieldDescriptor = null; 1455 for (FieldDescriptor fd : repositoryDescriptor.schemaFields) { 1456 if (propertyName.equals(fd.field)) { 1457 fieldDescriptor = fd; 1458 break; 1459 } 1460 } 1461 if (fieldType.isListType()) { 1462 Type listFieldType = ((ListType) fieldType).getFieldType(); 1463 if (listFieldType.isSimpleType()) { 1464 /* 1465 * Simple list. 1466 */ 1467 PropertyType propertyType = PropertyType.fromFieldType(listFieldType, true); 1468 boolean useArray = false; 1469 ColumnType columnType = null; 1470 if (repositoryDescriptor.getArrayColumns() && fieldDescriptor == null) { 1471 fieldDescriptor = new FieldDescriptor(); 1472 fieldDescriptor.type = FIELD_TYPE_ARRAY; 1473 } 1474 if (fieldDescriptor != null) { 1475 if (FIELD_TYPE_ARRAY.equals(fieldDescriptor.type)) { 1476 if (!supportsArrayColumns) { 1477 log.warn(" Field '" + propertyName + "' array specification is ignored since" 1478 + " this database does not support arrays"); 1479 } 1480 useArray = supportsArrayColumns; 1481 columnType = ColumnType.fromFieldType(listFieldType, useArray); 1482 } else if (FIELD_TYPE_ARRAY_LARGETEXT.equals(fieldDescriptor.type)) { 1483 boolean isStringColSpec = ColumnType.fromFieldType( 1484 listFieldType).spec == ColumnSpec.STRING; 1485 if (supportsArrayColumns && !isStringColSpec) { 1486 log.warn(" Field '" + propertyName + "' is not a String yet it is specified" 1487 + " as array_largetext, using ARRAY_CLOB for it"); 1488 } else if (!supportsArrayColumns && isStringColSpec) { 1489 log.warn(" Field '" + propertyName + "' array specification is ignored since" 1490 + " this database does not support arrays," + " using CLOB for it"); 1491 } else if (!supportsArrayColumns && !isStringColSpec) { 1492 log.warn(" Field '" + propertyName + "' array specification is ignored since" 1493 + " this database does not support arrays, also" 1494 + " Field is not a String yet it is specified" 1495 + " as array_largetext, using CLOB for it"); 1496 } 1497 useArray = supportsArrayColumns; 1498 columnType = (supportsArrayColumns) ? ColumnType.ARRAY_CLOB : ColumnType.CLOB; 1499 } else if (FIELD_TYPE_LARGETEXT.equals(fieldDescriptor.type)) { 1500 if (ColumnType.fromFieldType(listFieldType).spec != ColumnSpec.STRING) { 1501 log.warn(" Field '" + propertyName + "' is not a String yet it is specified " 1502 + " as largetext, using CLOB for it"); 1503 } 1504 columnType = ColumnType.CLOB; 1505 } else { 1506 log.warn(" Field '" + propertyName + "' specified but not successfully mapped"); 1507 } 1508 } 1509 1510 if (columnType == null) { 1511 columnType = ColumnType.fromFieldType(listFieldType); 1512 } 1513 log.debug(" List field '" + propertyName + "' using column type " + columnType); 1514 1515 if (useArray) { 1516 /* 1517 * Array: use an array. 1518 */ 1519 String fragmentName = typeFragmentName(complexType); 1520 String fragmentKey = field.getName().getLocalName(); 1521 addPropertyInfo(complexType, propertyName, propertyType, fragmentName, fragmentKey, false, 1522 null, columnType); 1523 addFieldFragment(field, fragmentName); 1524 } else { 1525 /* 1526 * Array: use a collection table. 1527 */ 1528 String fragmentName = collectionFragmentName(propertyName); 1529 addPropertyInfo(complexType, propertyName, propertyType, fragmentName, COLL_TABLE_VALUE_KEY, 1530 false, null, columnType); 1531 // pseudo-syntax with ending "#" to get to the pos column 1532 String posPropertyName = propertyName + "#"; 1533 PropertyType posPropertyType = PropertyType.LONG; 1534 addPropertyInfo(complexType, posPropertyName, posPropertyType, fragmentName, 1535 COLL_TABLE_POS_KEY, false, null, ColumnType.INTEGER); 1536 1537 Map<String, ColumnType> keysType = new LinkedHashMap<>(); 1538 keysType.put(COLL_TABLE_POS_KEY, ColumnType.INTEGER); 1539 keysType.put(COLL_TABLE_VALUE_KEY, columnType); 1540 addCollectionFragmentInfos(fragmentName, propertyType, COLL_TABLE_POS_KEY, keysType); 1541 1542 fragmentNames.add(fragmentName); 1543 addFieldFragment(field, fragmentName); 1544 } 1545 } else { 1546 /* 1547 * Complex list. 1548 */ 1549 initComplexTypeModel((ComplexType) listFieldType); 1550 } 1551 } else { 1552 /* 1553 * Primitive type. 1554 */ 1555 String fragmentName = typeFragmentName(complexType); 1556 String fragmentKey = field.getName().getLocalName(); 1557 PropertyType propertyType = PropertyType.fromFieldType(fieldType, false); 1558 ColumnType type = ColumnType.fromField(field); 1559 if (type.spec == ColumnSpec.STRING) { 1560 // backward compat with largetext, since 5.4.2 1561 if (fieldDescriptor != null && FIELD_TYPE_LARGETEXT.equals(fieldDescriptor.type)) { 1562 if (!type.isUnconstrained() && !type.isClob()) { 1563 log.warn(" String field '" + propertyName + "' has a schema constraint to " + type 1564 + " but is specified as largetext," + " using CLOB for it"); 1565 } 1566 type = ColumnType.CLOB; 1567 } 1568 } 1569 if (fieldDescriptor != null) { 1570 if (fieldDescriptor.table != null) { 1571 fragmentName = fieldDescriptor.table; 1572 } 1573 if (fieldDescriptor.column != null) { 1574 fragmentKey = fieldDescriptor.column; 1575 } 1576 } 1577 if (MAIN_KEY.equalsIgnoreCase(fragmentKey)) { 1578 String msg = "A property cannot be named '" + fragmentKey 1579 + "' because this is a reserved name, in type: " + typeName; 1580 throw new NuxeoException(msg); 1581 } 1582 if (fragmentName.equals(UID_SCHEMA_NAME) && (fragmentKey.equals(UID_MAJOR_VERSION_KEY) 1583 || fragmentKey.equals(UID_MINOR_VERSION_KEY))) { 1584 // workaround: special-case the "uid" schema, put 1585 // major/minor in the hierarchy table 1586 fragmentName = HIER_TABLE_NAME; 1587 fragmentKey = fragmentKey.equals(UID_MAJOR_VERSION_KEY) ? MAIN_MAJOR_VERSION_KEY 1588 : MAIN_MINOR_VERSION_KEY; 1589 } 1590 addPropertyInfo(complexType, propertyName, propertyType, fragmentName, fragmentKey, false, null, 1591 type); 1592 if (!fragmentName.equals(HIER_TABLE_NAME)) { 1593 fragmentNames.add(fragmentName); 1594 addFieldFragment(field, fragmentName); 1595 } 1596 } 1597 } 1598 } 1599 1600 if (isSchema) { 1601 schemaFragments.put(typeName, fragmentNames); 1602 schemaComplexChildren.put(typeName, complexChildren); 1603 } else { 1604 addTypeFragments(typeName, fragmentNames); 1605 typeComplexChildren.put(typeName, complexChildren); 1606 } 1607 } 1608 1609 private static String typeFragmentName(ComplexType type) { 1610 return type.getName(); 1611 } 1612 1613 private static String collectionFragmentName(String propertyName) { 1614 return propertyName; 1615 } 1616}