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