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 } 761 } 762 done.remove(typeName); 763 } 764 765 private void inferFulltextInfoByFragment() { 766 // simple fragments 767 for (Entry<String, Map<String, ModelProperty>> es : fragmentPropertyInfos.entrySet()) { 768 String fragmentName = es.getKey(); 769 Map<String, ModelProperty> infos = es.getValue(); 770 if (infos == null) { 771 continue; 772 } 773 PropertyType type = null; 774 for (ModelProperty info : infos.values()) { 775 if (info != null && info.fulltext) { 776 PropertyType t = info.propertyType; 777 if (t == PropertyType.STRING || t == PropertyType.BINARY) { 778 if (type == null) { 779 type = t; 780 continue; 781 } 782 if (type != t) { 783 type = PropertyType.BOOLEAN; // both 784 break; 785 } 786 } 787 } 788 } 789 fulltextInfoByFragment.put(fragmentName, type); 790 } 791 // collection fragments 792 for (Entry<String, PropertyType> es : collectionTables.entrySet()) { 793 String fragmentName = es.getKey(); 794 PropertyType type = es.getValue(); 795 if (type == PropertyType.ARRAY_STRING || type == PropertyType.ARRAY_BINARY) { 796 fulltextInfoByFragment.put(fragmentName, type.getArrayBaseType()); 797 } 798 } 799 } 800 801 /** Get doctype/complextype property info. */ 802 public ModelProperty getPropertyInfo(String typeName, String propertyName) { 803 Map<String, ModelProperty> propertyInfos = typePropertyInfos.get(typeName); 804 if (propertyInfos == null) { 805 // no such doctype/complextype 806 return null; 807 } 808 ModelProperty propertyInfo = propertyInfos.get(propertyName); 809 return propertyInfo != null ? propertyInfo : sharedPropertyInfos.get(propertyName); 810 } 811 812 public Map<String, ModelProperty> getMixinPropertyInfos(String mixin) { 813 return mixinPropertyInfos.get(mixin); 814 } 815 816 // for all types for now 817 public ModelProperty getProxySchemasPropertyInfo(String propertyName) { 818 ModelProperty propertyInfo = proxyPropertyInfos.get(propertyName); 819 return propertyInfo != null ? propertyInfo : sharedPropertyInfos.get(propertyName); 820 } 821 822 public ModelProperty getMixinPropertyInfo(String mixin, String propertyName) { 823 Map<String, ModelProperty> propertyInfos = mixinPropertyInfos.get(mixin); 824 if (propertyInfos == null) { 825 // no such mixin 826 return null; 827 } 828 return propertyInfos.get(propertyName); 829 } 830 831 public ModelProperty getPropertyInfo(String propertyName) { 832 return mergedPropertyInfos.get(propertyName); 833 } 834 835 /** 836 * Gets the model of the property for the given path. Returns something with 837 * {@link ModelProperty#isIntermediateSegment()} = true for an intermediate segment of a complex property. 838 */ 839 public ModelProperty getPathPropertyInfo(String xpath) { 840 return allPathPropertyInfos.get(xpath); 841 } 842 843 public Set<String> getPropertyInfoNames() { 844 return mergedPropertyInfos.keySet(); 845 } 846 847 public ModelProperty getPathPropertyInfo(String primaryType, String[] mixinTypes, String path) { 848 for (String schema : getAllSchemas(primaryType, mixinTypes)) { 849 Map<String, ModelProperty> propertyInfoByPath = schemaPathPropertyInfos.get(schema); 850 if (propertyInfoByPath != null) { 851 ModelProperty pi = propertyInfoByPath.get(path); 852 if (pi != null) { 853 return pi; 854 } 855 } 856 } 857 return null; 858 } 859 860 public Map<String, String> getTypeComplexChildren(String typeName) { 861 return typeComplexChildren.get(typeName); 862 } 863 864 public Map<String, String> getMixinComplexChildren(String mixin) { 865 return mixinComplexChildren.get(mixin); 866 } 867 868 public Set<String> getSimpleTextPropertyPaths(String primaryType, String[] mixinTypes) { 869 Set<String> paths = new HashSet<String>(); 870 for (String schema : getAllSchemas(primaryType, mixinTypes)) { 871 Set<String> p = schemaSimpleTextPaths.get(schema); 872 if (p != null) { 873 paths.addAll(p); 874 } 875 } 876 return paths; 877 } 878 879 /** 880 * Checks if the given xpath, when resolved on a proxy, points to a proxy-specific schema instead of the target 881 * document. 882 */ 883 public boolean isProxySchemaPath(String xpath) { 884 int p = xpath.indexOf(':'); 885 if (p == -1) { 886 return false; // no schema/prefix -> not on proxy 887 } 888 String prefix = xpath.substring(0, p); 889 String schema = prefixToSchema.get(prefix); 890 if (schema == null) { 891 schema = prefix; 892 } 893 return allProxySchemas.contains(schema); 894 } 895 896 private Set<String> getAllSchemas(String primaryType, String[] mixinTypes) { 897 Set<String> schemas = new LinkedHashSet<String>(); 898 Set<String> s = allDocTypeSchemas.get(primaryType); 899 if (s != null) { 900 schemas.addAll(s); 901 } 902 for (String mixin : mixinTypes) { 903 s = allMixinSchemas.get(mixin); 904 if (s != null) { 905 schemas.addAll(s); 906 } 907 } 908 return schemas; 909 } 910 911 public FulltextConfiguration getFulltextConfiguration() { 912 return fulltextConfiguration; 913 } 914 915 /** 916 * Finds out if a field is to be indexed as fulltext. 917 * 918 * @param fragmentName 919 * @param fragmentKey the key or {@code null} for a collection 920 * @return {@link PropertyType#STRING} or {@link PropertyType#BINARY} if this field is to be indexed as fulltext 921 */ 922 public PropertyType getFulltextFieldType(String fragmentName, String fragmentKey) { 923 if (fragmentKey == null) { 924 PropertyType type = collectionTables.get(fragmentName); 925 if (type == PropertyType.ARRAY_STRING || type == PropertyType.ARRAY_BINARY) { 926 return type.getArrayBaseType(); 927 } 928 return null; 929 } else { 930 Map<String, ModelProperty> infos = fragmentPropertyInfos.get(fragmentName); 931 if (infos == null) { 932 return null; 933 } 934 ModelProperty info = infos.get(fragmentKey); 935 if (info != null && info.fulltext) { 936 return info.propertyType; 937 } 938 return null; 939 } 940 } 941 942 /** 943 * Checks if a fragment has any field indexable as fulltext. 944 * 945 * @param fragmentName 946 * @return PropertyType.STRING, PropertyType.BINARY, or PropertyType.BOOLEAN for both. 947 */ 948 public PropertyType getFulltextInfoForFragment(String fragmentName) { 949 return fulltextInfoByFragment.get(fragmentName); 950 } 951 952 public Type getSpecialPropertyType(String propertyName) { 953 return specialPropertyTypes.get(propertyName); 954 } 955 956 public PropertyType getCollectionFragmentType(String fragmentName) { 957 return collectionTables.get(fragmentName); 958 } 959 960 public boolean isCollectionFragment(String fragmentName) { 961 return collectionTables.containsKey(fragmentName); 962 } 963 964 public String getCollectionOrderBy(String fragmentName) { 965 return collectionOrderBy.get(fragmentName); 966 } 967 968 public Set<String> getFragmentNames() { 969 return fragmentKeyTypes.keySet(); 970 } 971 972 public Map<String, ColumnType> getFragmentKeysType(String fragmentName) { 973 return fragmentKeyTypes.get(fragmentName); 974 } 975 976 public Map<String, List<String>> getBinaryPropertyInfos() { 977 return binaryFragmentKeys; 978 } 979 980 private void addTypeFragments(String typeName, Set<String> fragmentNames) { 981 typeFragments.put(typeName, fragmentNames); 982 } 983 984 private void addFieldFragment(Field field, String fragmentName) { 985 String fieldName = field.getName().toString(); 986 fieldFragment.put(fieldName, fragmentName); 987 } 988 989 private void addDocTypePrefetchedFragments(String docTypeName, Set<String> fragmentNames) { 990 Set<String> fragments = docTypePrefetchedFragments.get(docTypeName); 991 if (fragments == null) { 992 docTypePrefetchedFragments.put(docTypeName, fragments = new HashSet<String>()); 993 } 994 fragments.addAll(fragmentNames); 995 } 996 997 /** 998 * Gets the fragments for a type (doctype or complex type). 999 */ 1000 private Set<String> getTypeFragments(String docTypeName) { 1001 return typeFragments.get(docTypeName); 1002 } 1003 1004 /** 1005 * Gets the fragments for a mixin. 1006 */ 1007 private Set<String> getMixinFragments(String mixin) { 1008 return mixinFragments.get(mixin); 1009 } 1010 1011 public Set<String> getTypePrefetchedFragments(String typeName) { 1012 return docTypePrefetchedFragments.get(typeName); 1013 } 1014 1015 /** 1016 * Checks if we have a type (doctype or complex type). 1017 */ 1018 public boolean isType(String typeName) { 1019 return typeFragments.containsKey(typeName); 1020 } 1021 1022 public String getDocumentSuperType(String typeName) { 1023 return documentSuperTypes.get(typeName); 1024 } 1025 1026 public Set<String> getDocumentSubTypes(String typeName) { 1027 return documentSubTypes.get(typeName); 1028 } 1029 1030 public Set<String> getDocumentTypes() { 1031 return documentTypesMixins.keySet(); 1032 } 1033 1034 public Set<String> getDocumentTypeFacets(String typeName) { 1035 Set<String> facets = documentTypesMixins.get(typeName); 1036 return facets == null ? Collections.<String> emptySet() : facets; 1037 } 1038 1039 public Set<String> getMixinDocumentTypes(String mixin) { 1040 Set<String> types = mixinsDocumentTypes.get(mixin); 1041 return types == null ? Collections.<String> emptySet() : types; 1042 } 1043 1044 /** 1045 * Given a map of id to types, returns a map of fragment names to ids. 1046 */ 1047 public Map<String, Set<Serializable>> getPerFragmentIds(Map<Serializable, IdWithTypes> idToTypes) { 1048 Map<String, Set<Serializable>> allFragmentIds = new HashMap<String, Set<Serializable>>(); 1049 for (Entry<Serializable, IdWithTypes> e : idToTypes.entrySet()) { 1050 Serializable id = e.getKey(); 1051 IdWithTypes typeInfo = e.getValue(); 1052 for (String fragmentName : getTypeFragments(typeInfo)) { 1053 Set<Serializable> fragmentIds = allFragmentIds.get(fragmentName); 1054 if (fragmentIds == null) { 1055 allFragmentIds.put(fragmentName, fragmentIds = new HashSet<Serializable>()); 1056 } 1057 fragmentIds.add(id); 1058 } 1059 } 1060 return allFragmentIds; 1061 } 1062 1063 /** 1064 * Gets the type fragments for a primary type and mixin types. Hierarchy is included. 1065 */ 1066 public Set<String> getTypeFragments(IdWithTypes typeInfo) { 1067 Set<String> fragmentNames = new HashSet<String>(); 1068 fragmentNames.add(HIER_TABLE_NAME); 1069 Set<String> tf = getTypeFragments(typeInfo.primaryType); 1070 if (tf != null) { 1071 // null if unknown type left in the database 1072 fragmentNames.addAll(tf); 1073 } 1074 String[] mixins = typeInfo.mixinTypes; 1075 if (mixins != null) { 1076 for (String mixin : mixins) { 1077 Set<String> mf = getMixinFragments(mixin); 1078 if (mf != null) { 1079 fragmentNames.addAll(mf); 1080 } 1081 } 1082 } 1083 if (PROXY_TYPE.equals(typeInfo.primaryType)) { 1084 fragmentNames.addAll(proxyFragments); 1085 } 1086 return fragmentNames; 1087 } 1088 1089 public Set<String> getNoPerDocumentQueryFacets() { 1090 return noPerDocumentQueryFacets; 1091 } 1092 1093 /** 1094 * Creates all the models. 1095 */ 1096 private void initModels() { 1097 SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class); 1098 log.debug("Schemas fields from descriptor: " + repositoryDescriptor.schemaFields); 1099 // document types 1100 for (DocumentType docType : schemaManager.getDocumentTypes()) { 1101 initDocTypeOrMixinModel(docType.getName(), docType.getSchemas(), allDocTypeSchemas, typeFragments, 1102 typePropertyInfos, typeComplexChildren, true); 1103 initDocTypePrefetch(docType); 1104 initDocTypeMixins(docType); 1105 inferSuperType(docType); 1106 } 1107 // mixins 1108 for (CompositeType type : schemaManager.getFacets()) { 1109 initDocTypeOrMixinModel(type.getName(), type.getSchemas(), allMixinSchemas, mixinFragments, 1110 mixinPropertyInfos, mixinComplexChildren, false); 1111 log.debug("Fragments for facet " + type.getName() + ": " + getMixinFragments(type.getName())); 1112 } 1113 // proxy schemas 1114 initProxySchemas(schemaManager.getProxySchemas(null)); 1115 // second pass to get subtypes (needs all supertypes) 1116 for (DocumentType documentType : schemaManager.getDocumentTypes()) { 1117 inferSubTypes(documentType); 1118 } 1119 // init no per instance query facets 1120 initNoPerDocumentQueryFacets(schemaManager); 1121 } 1122 1123 private void initProxySchemas(List<Schema> proxySchemas) { 1124 Map<String, Set<String>> allSchemas = new HashMap<String, Set<String>>(); 1125 Map<String, Set<String>> allFragments = new HashMap<String, Set<String>>(); 1126 Map<String, Map<String, String>> allChildren = new HashMap<String, Map<String, String>>(); 1127 Map<String, Map<String, ModelProperty>> allPropertyInfos = new HashMap<String, Map<String, ModelProperty>>(); 1128 String key = "__proxies__"; // not stored 1129 initDocTypeOrMixinModel(key, proxySchemas, allSchemas, allFragments, allPropertyInfos, allChildren, false); 1130 allProxySchemas.addAll(allSchemas.get(key)); 1131 proxyFragments.addAll(allFragments.get(key)); 1132 proxyPropertyInfos.putAll(allPropertyInfos.get(key)); 1133 typeComplexChildren.put(PROXY_TYPE, allChildren.get(key)); 1134 } 1135 1136 private Set<String> getCommonFragments(String typeName) { 1137 Set<String> fragments = new HashSet<String>(5); 1138 fragments.add(VERSION_TABLE_NAME); 1139 fragments.add(ACL_TABLE_NAME); 1140 if (!miscInHierarchy) { 1141 fragments.add(MISC_TABLE_NAME); 1142 } 1143 if (fulltextConfiguration != null && fulltextConfiguration.isFulltextIndexable(typeName)) { 1144 fragments.add(FULLTEXT_TABLE_NAME); 1145 } 1146 return fragments; 1147 } 1148 1149 private Set<String> getCommonFragmentsPrefetched() { 1150 Set<String> fragments = new HashSet<String>(5); 1151 fragments.add(VERSION_TABLE_NAME); 1152 fragments.add(ACL_TABLE_NAME); 1153 if (!miscInHierarchy) { 1154 fragments.add(MISC_TABLE_NAME); 1155 } 1156 return fragments; 1157 } 1158 1159 /** 1160 * For a doctype or mixin type, init the schemas-related structures. 1161 */ 1162 private void initDocTypeOrMixinModel(String typeName, Collection<Schema> schemas, 1163 Map<String, Set<String>> schemasMap, Map<String, Set<String>> fragmentsMap, 1164 Map<String, Map<String, ModelProperty>> propertyInfoMap, 1165 Map<String, Map<String, String>> complexChildrenMap, boolean addCommonFragments) { 1166 Set<String> schemaNames = new HashSet<String>(); 1167 Set<String> fragmentNames = new HashSet<String>(); 1168 Map<String, String> complexChildren = new HashMap<String, String>(); 1169 if (addCommonFragments) { 1170 fragmentNames.addAll(getCommonFragments(typeName)); 1171 } 1172 for (Schema schema : schemas) { 1173 if (schema == null) { 1174 // happens when a type refers to a nonexistent schema 1175 // TODO log and avoid nulls earlier 1176 continue; 1177 } 1178 schemaNames.add(schema.getName()); 1179 try { 1180 fragmentNames.addAll(initSchemaModel(schema)); 1181 } catch (NuxeoException e) { 1182 e.addInfo(String.format("Error initializing schema '%s' for composite type '%s'", schema.getName(), 1183 typeName)); 1184 throw e; 1185 } 1186 inferSchemaPropertyPaths(schema); 1187 complexChildren.putAll(schemaComplexChildren.get(schema.getName())); 1188 } 1189 schemasMap.put(typeName, schemaNames); 1190 fragmentsMap.put(typeName, fragmentNames); 1191 complexChildrenMap.put(typeName, complexChildren); 1192 inferPropertyInfos(propertyInfoMap, typeName, schemaNames); 1193 } 1194 1195 private void initDocTypePrefetch(DocumentType docType) { 1196 String docTypeName = docType.getName(); 1197 PrefetchInfo prefetch = docType.getPrefetchInfo(); 1198 if (prefetch != null) { 1199 Set<String> documentTypeFragments = getTypeFragments(docTypeName); 1200 for (String fieldName : prefetch.getFields()) { 1201 // prefetch all the relevant fragments 1202 // TODO deal with full xpath 1203 String fragment = fieldFragment.get(fieldName); 1204 if (fragment != null) { 1205 // checks that the field actually belongs 1206 // to the type 1207 if (documentTypeFragments.contains(fragment)) { 1208 addDocTypePrefetchedFragments(docTypeName, Collections.singleton(fragment)); 1209 } 1210 } 1211 } 1212 for (String schemaName : prefetch.getSchemas()) { 1213 Set<String> fragments = schemaFragments.get(schemaName); 1214 if (fragments != null) { 1215 addDocTypePrefetchedFragments(docTypeName, fragments); 1216 } 1217 } 1218 } 1219 // always prefetch ACLs, versions, misc (for lifecycle) 1220 addDocTypePrefetchedFragments(docTypeName, getCommonFragmentsPrefetched()); 1221 1222 log.debug("Fragments for type " + docTypeName + ": " + getTypeFragments(docTypeName) + ", prefetch: " 1223 + getTypePrefetchedFragments(docTypeName)); 1224 } 1225 1226 private void initDocTypeMixins(DocumentType docType) { 1227 String docTypeName = docType.getName(); 1228 Set<String> mixins = docType.getFacets(); 1229 documentTypesMixins.put(docTypeName, new HashSet<String>(mixins)); 1230 for (String mixin : mixins) { 1231 Set<String> mixinTypes = mixinsDocumentTypes.get(mixin); 1232 if (mixinTypes == null) { 1233 mixinsDocumentTypes.put(mixin, mixinTypes = new HashSet<String>()); 1234 } 1235 mixinTypes.add(docTypeName); 1236 } 1237 } 1238 1239 private void inferSuperType(DocumentType docType) { 1240 Type superType = docType.getSuperType(); 1241 if (superType != null) { 1242 documentSuperTypes.put(docType.getName(), superType.getName()); 1243 } 1244 } 1245 1246 private void inferSubTypes(DocumentType docType) { 1247 String type = docType.getName(); 1248 String superType = type; 1249 do { 1250 Set<String> subTypes = documentSubTypes.get(superType); 1251 if (subTypes == null) { 1252 documentSubTypes.put(superType, subTypes = new HashSet<String>()); 1253 } 1254 subTypes.add(type); 1255 superType = documentSuperTypes.get(superType); 1256 } while (superType != null); 1257 } 1258 1259 private void initNoPerDocumentQueryFacets(SchemaManager schemaManager) { 1260 noPerDocumentQueryFacets.addAll(schemaManager.getNoPerDocumentQueryFacets()); 1261 } 1262 1263 /** 1264 * Special model for the main table (the one containing the primary type information). 1265 * <p> 1266 * If the main table is not separate from the hierarchy table, then it's will not really be instantiated by itself 1267 * but merged into the hierarchy table. 1268 */ 1269 private void initMainModel() { 1270 addPropertyInfo(MAIN_PRIMARY_TYPE_PROP, PropertyType.STRING, HIER_TABLE_NAME, MAIN_PRIMARY_TYPE_KEY, true, null, 1271 ColumnType.SYSNAME); 1272 addPropertyInfo(MAIN_MIXIN_TYPES_PROP, PropertyType.STRING, HIER_TABLE_NAME, MAIN_MIXIN_TYPES_KEY, false, null, 1273 ColumnType.SYSNAMEARRAY); 1274 addPropertyInfo(MAIN_CHECKED_IN_PROP, PropertyType.BOOLEAN, HIER_TABLE_NAME, MAIN_CHECKED_IN_KEY, false, 1275 BooleanType.INSTANCE, ColumnType.BOOLEAN); 1276 addPropertyInfo(MAIN_BASE_VERSION_PROP, idPropertyType, HIER_TABLE_NAME, MAIN_BASE_VERSION_KEY, false, 1277 idCoreType, ColumnType.NODEVAL); 1278 addPropertyInfo(MAIN_MAJOR_VERSION_PROP, PropertyType.LONG, HIER_TABLE_NAME, MAIN_MAJOR_VERSION_KEY, false, 1279 LongType.INSTANCE, ColumnType.INTEGER); 1280 addPropertyInfo(MAIN_MINOR_VERSION_PROP, PropertyType.LONG, HIER_TABLE_NAME, MAIN_MINOR_VERSION_KEY, false, 1281 LongType.INSTANCE, ColumnType.INTEGER); 1282 addPropertyInfo(MAIN_IS_VERSION_PROP, PropertyType.BOOLEAN, HIER_TABLE_NAME, MAIN_IS_VERSION_KEY, false, 1283 BooleanType.INSTANCE, ColumnType.BOOLEAN); 1284 if (softDeleteEnabled) { 1285 addPropertyInfo(MAIN_IS_DELETED_PROP, PropertyType.BOOLEAN, HIER_TABLE_NAME, MAIN_IS_DELETED_KEY, true, 1286 BooleanType.INSTANCE, ColumnType.BOOLEAN); 1287 addPropertyInfo(MAIN_DELETED_TIME_PROP, PropertyType.DATETIME, HIER_TABLE_NAME, MAIN_DELETED_TIME_KEY, true, 1288 DateType.INSTANCE, ColumnType.TIMESTAMP); 1289 } 1290 } 1291 1292 /** 1293 * Special model for the "misc" table (lifecycle, dirty.). 1294 */ 1295 private void initMiscModel() { 1296 String fragmentName = miscInHierarchy ? HIER_TABLE_NAME : MISC_TABLE_NAME; 1297 addPropertyInfo(MISC_LIFECYCLE_POLICY_PROP, PropertyType.STRING, fragmentName, MISC_LIFECYCLE_POLICY_KEY, false, 1298 StringType.INSTANCE, ColumnType.SYSNAME); 1299 addPropertyInfo(MISC_LIFECYCLE_STATE_PROP, PropertyType.STRING, fragmentName, MISC_LIFECYCLE_STATE_KEY, false, 1300 StringType.INSTANCE, ColumnType.SYSNAME); 1301 } 1302 1303 /** 1304 * Special model for the versions table. 1305 */ 1306 private void initVersionsModel() { 1307 addPropertyInfo(VERSION_VERSIONABLE_PROP, idPropertyType, VERSION_TABLE_NAME, VERSION_VERSIONABLE_KEY, false, 1308 idCoreType, ColumnType.NODEVAL); 1309 addPropertyInfo(VERSION_CREATED_PROP, PropertyType.DATETIME, VERSION_TABLE_NAME, VERSION_CREATED_KEY, false, 1310 DateType.INSTANCE, ColumnType.TIMESTAMP); 1311 addPropertyInfo(VERSION_LABEL_PROP, PropertyType.STRING, VERSION_TABLE_NAME, VERSION_LABEL_KEY, false, 1312 StringType.INSTANCE, ColumnType.SYSNAME); 1313 addPropertyInfo(VERSION_DESCRIPTION_PROP, PropertyType.STRING, VERSION_TABLE_NAME, VERSION_DESCRIPTION_KEY, 1314 false, StringType.INSTANCE, ColumnType.STRING); 1315 addPropertyInfo(VERSION_IS_LATEST_PROP, PropertyType.BOOLEAN, VERSION_TABLE_NAME, VERSION_IS_LATEST_KEY, false, 1316 BooleanType.INSTANCE, ColumnType.BOOLEAN); 1317 addPropertyInfo(VERSION_IS_LATEST_MAJOR_PROP, PropertyType.BOOLEAN, VERSION_TABLE_NAME, 1318 VERSION_IS_LATEST_MAJOR_KEY, false, BooleanType.INSTANCE, ColumnType.BOOLEAN); 1319 } 1320 1321 /** 1322 * Special model for the proxies table. 1323 */ 1324 private void initProxiesModel() { 1325 String type = PROXY_TYPE; 1326 addPropertyInfo(type, PROXY_TARGET_PROP, idPropertyType, PROXY_TABLE_NAME, PROXY_TARGET_KEY, false, idCoreType, 1327 ColumnType.NODEIDFKNP); 1328 addPropertyInfo(type, PROXY_VERSIONABLE_PROP, idPropertyType, PROXY_TABLE_NAME, PROXY_VERSIONABLE_KEY, false, 1329 idCoreType, ColumnType.NODEVAL); 1330 addTypeFragments(type, Collections.singleton(PROXY_TABLE_NAME)); 1331 } 1332 1333 /** 1334 * Special model for the locks table (also, primary key has no foreign key, see {@link SQLInfo#initFragmentSQL}. 1335 */ 1336 private void initLocksModel() { 1337 addPropertyInfo(LOCK_OWNER_PROP, PropertyType.STRING, LOCK_TABLE_NAME, LOCK_OWNER_KEY, false, 1338 StringType.INSTANCE, ColumnType.SYSNAME); 1339 addPropertyInfo(LOCK_CREATED_PROP, PropertyType.DATETIME, LOCK_TABLE_NAME, LOCK_CREATED_KEY, false, 1340 DateType.INSTANCE, ColumnType.TIMESTAMP); 1341 } 1342 1343 /** 1344 * Special model for the fulltext table. 1345 */ 1346 private void initFullTextModel() { 1347 addPropertyInfo(FULLTEXT_JOBID_PROP, PropertyType.STRING, FULLTEXT_TABLE_NAME, FULLTEXT_JOBID_KEY, false, 1348 StringType.INSTANCE, ColumnType.SYSNAME); 1349 for (String indexName : fulltextConfiguration.indexNames) { 1350 String suffix = getFulltextIndexSuffix(indexName); 1351 if (materializeFulltextSyntheticColumn) { 1352 addPropertyInfo(FULLTEXT_FULLTEXT_PROP + suffix, PropertyType.STRING, FULLTEXT_TABLE_NAME, 1353 FULLTEXT_FULLTEXT_KEY + suffix, false, StringType.INSTANCE, ColumnType.FTINDEXED); 1354 } 1355 addPropertyInfo(FULLTEXT_SIMPLETEXT_PROP + suffix, PropertyType.STRING, FULLTEXT_TABLE_NAME, 1356 FULLTEXT_SIMPLETEXT_KEY + suffix, false, StringType.INSTANCE, ColumnType.FTSTORED); 1357 addPropertyInfo(FULLTEXT_BINARYTEXT_PROP + suffix, PropertyType.STRING, FULLTEXT_TABLE_NAME, 1358 FULLTEXT_BINARYTEXT_KEY + suffix, false, StringType.INSTANCE, ColumnType.FTSTORED); 1359 } 1360 } 1361 1362 public String getFulltextIndexSuffix(String indexName) { 1363 return indexName.equals(FULLTEXT_DEFAULT_INDEX) ? "" : '_' + indexName; 1364 } 1365 1366 /** 1367 * Special collection-like model for the ACL table. 1368 */ 1369 private void initAclModel() { 1370 Map<String, ColumnType> keysType = new LinkedHashMap<String, ColumnType>(); 1371 keysType.put(ACL_POS_KEY, ColumnType.INTEGER); 1372 keysType.put(ACL_NAME_KEY, ColumnType.SYSNAME); 1373 keysType.put(ACL_GRANT_KEY, ColumnType.BOOLEAN); 1374 keysType.put(ACL_PERMISSION_KEY, ColumnType.SYSNAME); 1375 keysType.put(ACL_CREATOR_KEY, ColumnType.SYSNAME); 1376 keysType.put(ACL_BEGIN_KEY, ColumnType.TIMESTAMP); 1377 keysType.put(ACL_END_KEY, ColumnType.TIMESTAMP); 1378 keysType.put(ACL_STATUS_KEY, ColumnType.LONG); 1379 keysType.put(ACL_USER_KEY, ColumnType.SYSNAME); 1380 keysType.put(ACL_GROUP_KEY, ColumnType.SYSNAME); 1381 String fragmentName = ACL_TABLE_NAME; 1382 addCollectionFragmentInfos(fragmentName, PropertyType.COLL_ACL, ACL_POS_KEY, keysType); 1383 addPropertyInfo(ACL_PROP, PropertyType.COLL_ACL, fragmentName, null, false, null, null); 1384 // for query 1385 // composed of NXQL.ECM_ACL and NXQL.ECM_ACL_PRINCIPAL etc. 1386 allPathPropertyInfos.put("ecm:acl.principal/*", 1387 new ModelProperty(PropertyType.STRING, fragmentName, ACL_USER_KEY, true)); 1388 allPathPropertyInfos.put("ecm:acl.permission/*", 1389 new ModelProperty(PropertyType.STRING, fragmentName, ACL_PERMISSION_KEY, true)); 1390 allPathPropertyInfos.put("ecm:acl.grant/*", 1391 new ModelProperty(PropertyType.BOOLEAN, fragmentName, ACL_GRANT_KEY, true)); 1392 allPathPropertyInfos.put("ecm:acl.name/*", 1393 new ModelProperty(PropertyType.STRING, fragmentName, ACL_NAME_KEY, true)); 1394 allPathPropertyInfos.put("ecm:acl.pos/*", 1395 new ModelProperty(PropertyType.LONG, fragmentName, ACL_POS_KEY, true)); 1396 allPathPropertyInfos.put("ecm:acl.creator/*", 1397 new ModelProperty(PropertyType.STRING, fragmentName, ACL_CREATOR_KEY, true)); 1398 allPathPropertyInfos.put("ecm:acl.begin/*", 1399 new ModelProperty(PropertyType.DATETIME, fragmentName, ACL_BEGIN_KEY, true)); 1400 allPathPropertyInfos.put("ecm:acl.end/*", 1401 new ModelProperty(PropertyType.DATETIME, fragmentName, ACL_END_KEY, true)); 1402 allPathPropertyInfos.put("ecm:acl.status/*", 1403 new ModelProperty(PropertyType.LONG, fragmentName, ACL_STATUS_KEY, true)); 1404 } 1405 1406 /** 1407 * Creates the model for a schema. 1408 */ 1409 private Set<String> initSchemaModel(Schema schema) { 1410 initComplexTypeModel(schema); 1411 return schemaFragments.get(schema.getName()); 1412 } 1413 1414 /** 1415 * Creates the model for a complex type or a schema. Recurses in complex types. 1416 * <p> 1417 * Adds the simple+collection fragments to {@link #typeFragments} or {@link #schemaFragments}. 1418 */ 1419 private void initComplexTypeModel(ComplexType complexType) { 1420 String typeName = complexType.getName(); 1421 boolean isSchema = complexType instanceof Schema; 1422 if (isSchema && schemaFragments.containsKey(typeName)) { 1423 return; 1424 } else if (!isSchema && typeFragments.containsKey(typeName)) { 1425 return; 1426 } 1427 /** The fragment names to use for this type, usually just one. */ 1428 Set<String> fragmentNames = new HashSet<String>(1); 1429 /** The children complex properties for this type. */ 1430 Map<String, String> complexChildren = new HashMap<String, String>(1); 1431 1432 log.debug("Making model for type " + typeName); 1433 1434 /** Initialized if this type has a table associated. */ 1435 for (Field field : complexType.getFields()) { 1436 Type fieldType = field.getType(); 1437 if (fieldType.isComplexType()) { 1438 /* 1439 * Complex type. 1440 */ 1441 String propertyName = field.getName().getPrefixedName(); 1442 complexChildren.put(propertyName, fieldType.getName()); 1443 initComplexTypeModel((ComplexType) fieldType); 1444 } else { 1445 String propertyName = field.getName().getPrefixedName(); 1446 FieldDescriptor fieldDescriptor = null; 1447 for (FieldDescriptor fd : repositoryDescriptor.schemaFields) { 1448 if (propertyName.equals(fd.field)) { 1449 fieldDescriptor = fd; 1450 break; 1451 } 1452 } 1453 if (fieldType.isListType()) { 1454 Type listFieldType = ((ListType) fieldType).getFieldType(); 1455 if (listFieldType.isSimpleType()) { 1456 /* 1457 * Simple list. 1458 */ 1459 PropertyType propertyType = PropertyType.fromFieldType(listFieldType, true); 1460 boolean useArray = false; 1461 ColumnType columnType = null; 1462 if (repositoryDescriptor.getArrayColumns() && fieldDescriptor == null) { 1463 fieldDescriptor = new FieldDescriptor(); 1464 fieldDescriptor.type = FIELD_TYPE_ARRAY; 1465 } 1466 if (fieldDescriptor != null) { 1467 if (FIELD_TYPE_ARRAY.equals(fieldDescriptor.type)) { 1468 if (!supportsArrayColumns) { 1469 log.warn(" Field '" + propertyName + "' array specification is ignored since" 1470 + " this database does not support arrays"); 1471 } 1472 useArray = supportsArrayColumns; 1473 columnType = ColumnType.fromFieldType(listFieldType, useArray); 1474 } else if (FIELD_TYPE_ARRAY_LARGETEXT.equals(fieldDescriptor.type)) { 1475 boolean isStringColSpec = ColumnType.fromFieldType( 1476 listFieldType).spec == ColumnSpec.STRING; 1477 if (supportsArrayColumns && !isStringColSpec) { 1478 log.warn(" Field '" + propertyName + "' is not a String yet it is specified" 1479 + " as array_largetext, using ARRAY_CLOB for it"); 1480 } else if (!supportsArrayColumns && isStringColSpec) { 1481 log.warn(" Field '" + propertyName + "' array specification is ignored since" 1482 + " this database does not support arrays," + " using CLOB for it"); 1483 } else if (!supportsArrayColumns && !isStringColSpec) { 1484 log.warn(" Field '" + propertyName + "' array specification is ignored since" 1485 + " this database does not support arrays, also" 1486 + " Field is not a String yet it is specified" 1487 + " as array_largetext, using CLOB for it"); 1488 } 1489 useArray = supportsArrayColumns; 1490 columnType = (supportsArrayColumns) ? ColumnType.ARRAY_CLOB : ColumnType.CLOB; 1491 } else if (FIELD_TYPE_LARGETEXT.equals(fieldDescriptor.type)) { 1492 if (ColumnType.fromFieldType(listFieldType).spec != ColumnSpec.STRING) { 1493 log.warn(" Field '" + propertyName + "' is not a String yet it is specified " 1494 + " as largetext, using CLOB for it"); 1495 } 1496 columnType = ColumnType.CLOB; 1497 } else { 1498 log.warn(" Field '" + propertyName + "' specified but not successfully mapped"); 1499 } 1500 } 1501 1502 if (columnType == null) { 1503 columnType = ColumnType.fromFieldType(listFieldType); 1504 } 1505 log.debug(" List field '" + propertyName + "' using column type " + columnType); 1506 1507 if (useArray) { 1508 /* 1509 * Array: use an array. 1510 */ 1511 String fragmentName = typeFragmentName(complexType); 1512 String fragmentKey = field.getName().getLocalName(); 1513 addPropertyInfo(complexType, propertyName, propertyType, fragmentName, fragmentKey, false, 1514 null, columnType); 1515 addFieldFragment(field, fragmentName); 1516 } else { 1517 /* 1518 * Array: use a collection table. 1519 */ 1520 String fragmentName = collectionFragmentName(propertyName); 1521 addPropertyInfo(complexType, propertyName, propertyType, fragmentName, COLL_TABLE_VALUE_KEY, 1522 false, null, columnType); 1523 1524 Map<String, ColumnType> keysType = new LinkedHashMap<String, ColumnType>(); 1525 keysType.put(COLL_TABLE_POS_KEY, ColumnType.INTEGER); 1526 keysType.put(COLL_TABLE_VALUE_KEY, columnType); 1527 addCollectionFragmentInfos(fragmentName, propertyType, COLL_TABLE_POS_KEY, keysType); 1528 1529 fragmentNames.add(fragmentName); 1530 addFieldFragment(field, fragmentName); 1531 } 1532 } else { 1533 /* 1534 * Complex list. 1535 */ 1536 initComplexTypeModel((ComplexType) listFieldType); 1537 } 1538 } else { 1539 /* 1540 * Primitive type. 1541 */ 1542 String fragmentName = typeFragmentName(complexType); 1543 String fragmentKey = field.getName().getLocalName(); 1544 PropertyType propertyType = PropertyType.fromFieldType(fieldType, false); 1545 ColumnType type = ColumnType.fromField(field); 1546 if (type.spec == ColumnSpec.STRING) { 1547 // backward compat with largetext, since 5.4.2 1548 if (fieldDescriptor != null && FIELD_TYPE_LARGETEXT.equals(fieldDescriptor.type)) { 1549 if (!type.isUnconstrained() && !type.isClob()) { 1550 log.warn(" String field '" + propertyName + "' has a schema constraint to " + type 1551 + " but is specified as largetext," + " using CLOB for it"); 1552 } 1553 type = ColumnType.CLOB; 1554 } 1555 } 1556 if (fieldDescriptor != null) { 1557 if (fieldDescriptor.table != null) { 1558 fragmentName = fieldDescriptor.table; 1559 } 1560 if (fieldDescriptor.column != null) { 1561 fragmentKey = fieldDescriptor.column; 1562 } 1563 } 1564 if (MAIN_KEY.equalsIgnoreCase(fragmentKey)) { 1565 String msg = "A property cannot be named '" + fragmentKey 1566 + "' because this is a reserved name, in type: " + typeName; 1567 throw new NuxeoException(msg); 1568 } 1569 if (fragmentName.equals(UID_SCHEMA_NAME) && (fragmentKey.equals(UID_MAJOR_VERSION_KEY) 1570 || fragmentKey.equals(UID_MINOR_VERSION_KEY))) { 1571 // workaround: special-case the "uid" schema, put 1572 // major/minor in the hierarchy table 1573 fragmentName = HIER_TABLE_NAME; 1574 fragmentKey = fragmentKey.equals(UID_MAJOR_VERSION_KEY) ? MAIN_MAJOR_VERSION_KEY 1575 : MAIN_MINOR_VERSION_KEY; 1576 } 1577 addPropertyInfo(complexType, propertyName, propertyType, fragmentName, fragmentKey, false, null, 1578 type); 1579 if (!fragmentName.equals(HIER_TABLE_NAME)) { 1580 fragmentNames.add(fragmentName); 1581 addFieldFragment(field, fragmentName); 1582 } 1583 } 1584 } 1585 } 1586 1587 if (isSchema) { 1588 schemaFragments.put(typeName, fragmentNames); 1589 schemaComplexChildren.put(typeName, complexChildren); 1590 } else { 1591 addTypeFragments(typeName, fragmentNames); 1592 typeComplexChildren.put(typeName, complexChildren); 1593 } 1594 } 1595 1596 private static String typeFragmentName(ComplexType type) { 1597 return type.getName(); 1598 } 1599 1600 private static String collectionFragmentName(String propertyName) { 1601 return propertyName; 1602 } 1603}