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