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