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