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