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