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