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