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}