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