/*
* Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Florent Guillaume
*/
package org.eclipse.ecr.core.storage.sql;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.ecr.core.schema.DocumentType;
import org.eclipse.ecr.core.schema.PrefetchInfo;
import org.eclipse.ecr.core.schema.SchemaManager;
import org.eclipse.ecr.core.schema.types.ComplexType;
import org.eclipse.ecr.core.schema.types.CompositeType;
import org.eclipse.ecr.core.schema.types.Field;
import org.eclipse.ecr.core.schema.types.ListType;
import org.eclipse.ecr.core.schema.types.Schema;
import org.eclipse.ecr.core.schema.types.Type;
import org.eclipse.ecr.core.schema.types.primitives.BooleanType;
import org.eclipse.ecr.core.schema.types.primitives.DateType;
import org.eclipse.ecr.core.schema.types.primitives.LongType;
import org.eclipse.ecr.core.schema.types.primitives.StringType;
import org.eclipse.ecr.core.storage.StorageException;
import org.eclipse.ecr.core.storage.sql.RepositoryDescriptor.FieldDescriptor;
import org.eclipse.ecr.core.storage.sql.RepositoryDescriptor.FulltextIndexDescriptor;
import org.eclipse.ecr.core.storage.sql.RowMapper.IdWithTypes;
/**
* The {@link Model} is the link between high-level types and SQL-level objects
* (entity tables, collections). It defines all policies relating to the choice
* of structure (what schema are grouped together in for optimization) and names
* in the SQL database (table names, column names), and to what entity names
* (type name, field name) they correspond.
* <p>
* A Nuxeo schema or type is mapped to a SQL-level table. Several types can be
* aggregated in the same table. In theory, a type could even be split into
* different tables.
*
* @author Florent Guillaume
*/
public class Model {
private static final Log log = LogFactory.getLog(Model.class);
public static final String ROOT_TYPE = "Root";
public static final String REPOINFO_TABLE_NAME = "repositories";
public static final String REPOINFO_REPONAME_KEY = "name";
public static final String MAIN_KEY = "id";
public static final String CLUSTER_NODES_TABLE_NAME = "cluster_nodes";
public static final String CLUSTER_NODES_NODEID_KEY = "nodeid";
public static final String CLUSTER_NODES_CREATED_KEY = "created";
public static final String CLUSTER_INVALS_TABLE_NAME = "cluster_invals";
public static final String CLUSTER_INVALS_NODEID_KEY = "nodeid";
public static final String CLUSTER_INVALS_ID_KEY = "id";
public static final String CLUSTER_INVALS_FRAGMENTS_KEY = "fragments";
public static final String CLUSTER_INVALS_KIND_KEY = "kind";
public static final String MAIN_PRIMARY_TYPE_PROP = "ecm:primaryType";
public static final String MAIN_PRIMARY_TYPE_KEY = "primarytype";
public static final String MAIN_MIXIN_TYPES_PROP = "ecm:mixinTypes";
public static final String MAIN_MIXIN_TYPES_KEY = "mixintypes";
public static final String MAIN_BASE_VERSION_PROP = "ecm:baseVersion";
public static final String MAIN_BASE_VERSION_KEY = "baseversionid";
public static final String MAIN_CHECKED_IN_PROP = "ecm:isCheckedIn";
public static final String MAIN_CHECKED_IN_KEY = "ischeckedin";
public static final String MAIN_MAJOR_VERSION_PROP = "ecm:majorVersion";
public static final String MAIN_MAJOR_VERSION_KEY = "majorversion";
public static final String MAIN_MINOR_VERSION_PROP = "ecm:minorVersion";
public static final String MAIN_MINOR_VERSION_KEY = "minorversion";
public static final String MAIN_IS_VERSION_PROP = "ecm:isVersion";
public static final String MAIN_IS_VERSION_KEY = "isversion";
public static final String UID_SCHEMA_NAME = "uid";
public static final String UID_MAJOR_VERSION_KEY = "major_version";
public static final String UID_MINOR_VERSION_KEY = "minor_version";
public static final String HIER_TABLE_NAME = "hierarchy";
public static final String HIER_PARENT_KEY = "parentid";
public static final String HIER_CHILD_NAME_KEY = "name";
public static final String HIER_CHILD_POS_KEY = "pos";
public static final String HIER_CHILD_ISPROPERTY_KEY = "isproperty";
public static final String ANCESTORS_TABLE_NAME = "ancestors";
public static final String ANCESTORS_ANCESTOR_KEY = "ancestors";
public static final String COLL_TABLE_POS_KEY = "pos";
public static final String COLL_TABLE_VALUE_KEY = "item";
public static final String MISC_TABLE_NAME = "misc";
public static final String MISC_LIFECYCLE_POLICY_PROP = "ecm:lifeCyclePolicy";
public static final String MISC_LIFECYCLE_POLICY_KEY = "lifecyclepolicy";
public static final String MISC_LIFECYCLE_STATE_PROP = "ecm:lifeCycleState";
public static final String MISC_LIFECYCLE_STATE_KEY = "lifecyclestate";
public static final String ACL_TABLE_NAME = "acls";
public static final String ACL_PROP = "ecm:acl";
public static final String ACL_POS_KEY = "pos";
public static final String ACL_NAME_KEY = "name";
public static final String ACL_GRANT_KEY = "grant";
public static final String ACL_PERMISSION_KEY = "permission";
public static final String ACL_USER_KEY = "user";
public static final String ACL_GROUP_KEY = "group";
public static final String VERSION_TABLE_NAME = "versions";
public static final String VERSION_VERSIONABLE_PROP = "ecm:versionableId";
public static final String VERSION_VERSIONABLE_KEY = "versionableid";
public static final String VERSION_CREATED_PROP = "ecm:versionCreated";
public static final String VERSION_CREATED_KEY = "created";
public static final String VERSION_LABEL_PROP = "ecm:versionLabel";
public static final String VERSION_LABEL_KEY = "label";
public static final String VERSION_DESCRIPTION_PROP = "ecm:versionDescription";
public static final String VERSION_DESCRIPTION_KEY = "description";
public static final String VERSION_IS_LATEST_PROP = "ecm:isLatestVersion";
public static final String VERSION_IS_LATEST_KEY = "islatest";
public static final String VERSION_IS_LATEST_MAJOR_PROP = "ecm:isLatestMajorVersion";
public static final String VERSION_IS_LATEST_MAJOR_KEY = "islatestmajor";
public static final String PROXY_TYPE = "ecm:proxy";
public static final String PROXY_TABLE_NAME = "proxies";
public static final String PROXY_TARGET_PROP = "ecm:proxyTargetId";
public static final String PROXY_TARGET_KEY = "targetid";
public static final String PROXY_VERSIONABLE_PROP = "ecm:proxyVersionableId";
public static final String PROXY_VERSIONABLE_KEY = "versionableid";
public static final String LOCK_TABLE_NAME = "locks";
public static final String LOCK_OWNER_PROP = "ecm:lockOwner";
public static final String LOCK_OWNER_KEY = "owner";
public static final String LOCK_CREATED_PROP = "ecm:lockCreated";
public static final String LOCK_CREATED_KEY = "created";
public static final String FULLTEXT_DEFAULT_INDEX = "default"; // not config
public static final String FULLTEXT_TABLE_NAME = "fulltext";
public static final String FULLTEXT_JOBID_PROP = "ecm:fulltextJobId";
public static final String FULLTEXT_JOBID_KEY = "jobid";
public static final String FULLTEXT_FULLTEXT_PROP = "ecm:fulltext";
public static final String FULLTEXT_FULLTEXT_KEY = "fulltext";
public static final String FULLTEXT_SIMPLETEXT_PROP = "ecm:simpleText";
public static final String FULLTEXT_SIMPLETEXT_KEY = "simpletext";
public static final String FULLTEXT_BINARYTEXT_PROP = "ecm:binaryText";
public static final String FULLTEXT_BINARYTEXT_KEY = "binarytext";
public static final String HIER_READ_ACL_TABLE_NAME = "hierarchy_read_acl";
public static final String HIER_READ_ACL_ID = "id";
public static final String HIER_READ_ACL_ACL_ID = "acl_id";
/** Specified in ext. point to use CLOBs. */
public static final String FIELD_TYPE_LARGETEXT = "largetext";
/**
* Special (non-schema-based) simple fragments present in all types.
* {@link #FULLTEXT_TABLE_NAME} is added to it if not disabled.
*/
public static final List<String> COMMON_SIMPLE_FRAGMENTS = Collections.singletonList(MISC_TABLE_NAME);
/** Special (non-schema-based) collection fragments present in all types. */
public static final String[] COMMON_COLLECTION_FRAGMENTS = { ACL_TABLE_NAME };
/** Fragments that are always prefetched. */
public static final String[] ALWAYS_PREFETCHED_FRAGMENTS = {
ACL_TABLE_NAME, VERSION_TABLE_NAME, MISC_TABLE_NAME };
protected final RepositoryDescriptor repositoryDescriptor;
// private final AtomicLong temporaryIdCounter;
/** Per-doctype list of schemas. */
private final Map<String, Set<String>> documentTypesSchemas;
/** Per-mixin list of document types. */
private final Map<String, Set<String>> mixinsDocumentTypes;
/** Per-mixin list of schemas. */
private final Map<String, Set<String>> mixinsSchemas;
/** Shared high-level properties that don't come from the schema manager. */
private final Map<String, Type> specialPropertyTypes;
/** Per-schema/type info about properties, using their key. */
private final HashMap<String, Map<String, ModelProperty>> schemaPropertyKeyInfos;
/** Per-schema/type info about properties. */
private final HashMap<String, Map<String, ModelProperty>> schemaPropertyInfos;
/** Per-mixin info about properties. */
private final HashMap<String, Map<String, ModelProperty>> mixinPropertyInfos;
/** Shared properties. */
private final Map<String, ModelProperty> sharedPropertyInfos;
/** Merged properties (all schemas together + shared). */
private final Map<String, ModelProperty> mergedPropertyInfos;
/** Per-schema map of path to property info. */
private final Map<String, Map<String, ModelProperty>> schemaPathPropertyInfos;
/** Per-schema set of path to simple fulltext properties. */
private final Map<String, Set<String>> schemaSimpleTextPaths;
/** Map of path (from all doc types) to property info. */
private final Map<String, ModelProperty> allPathPropertyInfos;
/** Per-table info about fragments keys type. */
private final Map<String, Map<String, ColumnType>> fragmentsKeys;
/** Maps collection table names to their type. */
private final Map<String, PropertyType> collectionTables;
/** Column ordering for collections. */
private final Map<String, String> collectionOrderBy;
/**
* The fragment for each schema, or {@code null} if the schema doesn't have
* a fragment.
*/
private final Map<String, String> schemaFragment;
/** Maps document type or schema to simple fragments. */
protected final Map<String, Set<String>> typeSimpleFragments;
/** Maps schema to collection fragments. */
protected final Map<String, Set<String>> typeCollectionFragments;
/** Maps schema to simple+collection fragments. */
protected final Map<String, Set<String>> typeFragments;
/** Maps mixin to simple+collection fragments. */
protected final Map<String, Set<String>> mixinFragments;
/** Maps document type or schema to prefetched fragments. */
protected final Map<String, Set<String>> typePrefetchedFragments;
/** Map of doc types to facets, for search. */
protected final Map<String, Set<String>> documentTypesFacets;
/** Map of doc type to its supertype, for search. */
protected final Map<String, String> documentSuperTypes;
/** Map of doc type to its subtypes (including itself), for search. */
protected final Map<String, Set<String>> documentSubTypes;
/** Map of field name to fragments holding them */
protected final Map<String, Set<String>> fieldFragments;
public final ModelFulltext fulltextInfo;
private final boolean materializeFulltextSyntheticColumn;
public Model(ModelSetup modelSetup) throws StorageException {
repositoryDescriptor = modelSetup.repositoryDescriptor;
materializeFulltextSyntheticColumn = modelSetup.materializeFulltextSyntheticColumn;
// temporaryIdCounter = new AtomicLong(0);
documentTypesSchemas = new HashMap<String, Set<String>>();
mixinsDocumentTypes = new HashMap<String, Set<String>>();
mixinsSchemas = new HashMap<String, Set<String>>();
schemaPropertyKeyInfos = new HashMap<String, Map<String, ModelProperty>>();
schemaPropertyInfos = new HashMap<String, Map<String, ModelProperty>>();
mixinPropertyInfos = new HashMap<String, Map<String, ModelProperty>>();
sharedPropertyInfos = new HashMap<String, ModelProperty>();
mergedPropertyInfos = new HashMap<String, ModelProperty>();
schemaPathPropertyInfos = new HashMap<String, Map<String, ModelProperty>>();
schemaSimpleTextPaths = new HashMap<String, Set<String>>();
allPathPropertyInfos = new HashMap<String, ModelProperty>();
fulltextInfo = new ModelFulltext();
fragmentsKeys = new HashMap<String, Map<String, ColumnType>>();
collectionTables = new HashMap<String, PropertyType>();
collectionOrderBy = new HashMap<String, String>();
schemaFragment = new HashMap<String, String>();
typeFragments = new HashMap<String, Set<String>>();
mixinFragments = new HashMap<String, Set<String>>();
typeSimpleFragments = new HashMap<String, Set<String>>();
typeCollectionFragments = new HashMap<String, Set<String>>();
typePrefetchedFragments = new HashMap<String, Set<String>>();
fieldFragments = new HashMap<String, Set<String>>();
documentTypesFacets = new HashMap<String, Set<String>>();
documentSuperTypes = new HashMap<String, String>();
documentSubTypes = new HashMap<String, Set<String>>();
specialPropertyTypes = new HashMap<String, Type>();
initMainModel();
initVersionsModel();
initProxiesModel();
initLocksModel();
initAclModel();
initMiscModel();
initModels(modelSetup.schemaManager);
if (!repositoryDescriptor.fulltextDisabled) {
initFullTextModel();
}
}
/**
* Gets the repository descriptor used for this model.
*
* @return the repository descriptor
*/
public RepositoryDescriptor getRepositoryDescriptor() {
return repositoryDescriptor;
}
/**
* Computes a new unique id.
* <p>
* If actual ids are computed by the database, this will be a temporary id,
* otherwise the final one.
*
* @return a new id, which may be temporary
*/
public Serializable generateNewId() {
return UUID.randomUUID().toString();
// return "UUID_" + temporaryIdCounter.incrementAndGet();
// return "T" + temporaryIdCounter.incrementAndGet();
}
/**
* Fixup an id that has been turned into a string for high-level Nuxeo APIs.
*
* @param id the id to fixup
* @return the fixed up id
*/
public Serializable unHackStringId(String id) {
return id;
// if (id.startsWith("T")) {
// return id;
// }
// Document ids coming from higher level have been turned into strings
// (by SQLDocument.getUUID) but are really longs for the backend.
// return Long.valueOf(id);
}
/**
* Records info about one property, in a schema-based structure and in a
* table-based structure.
* <p>
* If {@literal schemaName} is {@code null}, then the property applies to
* all types (system properties).
*/
private void addPropertyInfo(String schemaName, String propertyName,
PropertyType propertyType, String fragmentName, String fragmentKey,
boolean readonly, Type coreType, ColumnType type) {
// per-type
Map<String, ModelProperty> propertyKeyInfos;
Map<String, ModelProperty> propertyInfos;
if (schemaName == null) {
propertyKeyInfos = null;
propertyInfos = sharedPropertyInfos;
} else {
propertyKeyInfos = schemaPropertyKeyInfos.get(schemaName);
if (propertyKeyInfos == null) {
propertyKeyInfos = new HashMap<String, ModelProperty>();
schemaPropertyKeyInfos.put(schemaName, propertyKeyInfos);
}
propertyInfos = schemaPropertyInfos.get(schemaName);
if (propertyInfos == null) {
propertyInfos = new HashMap<String, ModelProperty>();
schemaPropertyInfos.put(schemaName, propertyInfos);
}
}
ModelProperty propertyInfo = new ModelProperty(propertyType,
fragmentName, fragmentKey, readonly);
propertyInfos.put(propertyName, propertyInfo);
if (propertyKeyInfos != null && fragmentKey != null) {
propertyKeyInfos.put(fragmentKey, propertyInfo);
}
// per-fragment keys type
if (fragmentKey != null) {
Map<String, ColumnType> fragmentKeys = fragmentsKeys.get(fragmentName);
if (fragmentKeys == null) {
fragmentsKeys.put(fragmentName,
fragmentKeys = new LinkedHashMap<String, ColumnType>());
}
fragmentKeys.put(fragmentKey, type);
}
// system properties
if (coreType != null) {
specialPropertyTypes.put(propertyName, coreType);
}
// merged properties
ModelProperty previous = mergedPropertyInfos.get(propertyName);
if (previous == null) {
mergedPropertyInfos.put(propertyName, propertyInfo);
} else {
log.debug(String.format(
"Schemas '%s' and '%s' both have a property '%s', "
+ "unqualified reference in queries will use schema '%1$s'",
previous.fragmentName, fragmentName, propertyName));
}
// compatibility for use of schema name as prefix
if (!propertyName.contains(":")) {
// allow schema name as prefix
propertyName = schemaName + ':' + propertyName;
previous = mergedPropertyInfos.get(propertyName);
if (previous == null) {
mergedPropertyInfos.put(propertyName, propertyInfo);
}
}
}
/**
* Infers type property information from all its schemas.
*/
private void inferTypePropertyInfos(String typeName, String[] schemaNames) {
Map<String, ModelProperty> propertyInfos = schemaPropertyInfos.get(typeName);
if (propertyInfos == null) {
propertyInfos = new HashMap<String, ModelProperty>();
schemaPropertyInfos.put(typeName, propertyInfos);
}
for (String schemaName : schemaNames) {
Map<String, ModelProperty> infos = schemaPropertyInfos.get(schemaName);
if (infos == null) {
// schema with no properties (complex list)
continue;
}
for (Entry<String, ModelProperty> info : infos.entrySet()) {
propertyInfos.put(info.getKey(), info.getValue());
}
}
}
/**
* Infers mixin property information from all its schemas.
*/
private void inferMixinPropertyInfos(String mixin, String[] schemaNames) {
Map<String, ModelProperty> propertyInfos = schemaPropertyInfos.get(mixin);
if (propertyInfos == null) {
mixinPropertyInfos.put(mixin,
propertyInfos = new HashMap<String, ModelProperty>());
}
for (String schemaName : schemaNames) {
Map<String, ModelProperty> infos = schemaPropertyInfos.get(schemaName);
if (infos == null) {
// schema with no properties (complex list)
continue;
}
for (Entry<String, ModelProperty> info : infos.entrySet()) {
propertyInfos.put(info.getKey(), info.getValue());
}
}
}
/**
* Infers all possible paths for properties in a document type.
*/
private void inferTypePropertyPaths(DocumentType documentType) {
for (Schema schema : documentType.getSchemas()) {
if (schema == null) {
// happens when a type refers to a nonexistent schema
// TODO log and avoid nulls earlier
continue;
}
inferSchemaPropertyPaths(schema);
}
}
/**
* Infers all possible paths for properties in a schema.
*/
private void inferSchemaPropertyPaths(Schema schema) {
String schemaName = schema.getName();
if (schemaPathPropertyInfos.containsKey(schemaName)) {
return;
}
Map<String, ModelProperty> propertyInfoByPath = new HashMap<String, ModelProperty>();
inferTypePropertyPaths(schema, "", propertyInfoByPath, null);
schemaPathPropertyInfos.put(schemaName, propertyInfoByPath);
allPathPropertyInfos.putAll(propertyInfoByPath);
// those for simpletext properties
Set<String> simplePaths = new HashSet<String>();
for (Entry<String, ModelProperty> entry : propertyInfoByPath.entrySet()) {
ModelProperty pi = entry.getValue();
if (pi.propertyType != PropertyType.STRING
&& pi.propertyType != PropertyType.ARRAY_STRING) {
continue;
}
simplePaths.add(entry.getKey());
}
schemaSimpleTextPaths.put(schemaName, simplePaths);
}
// recurses in a complex type
private void inferTypePropertyPaths(ComplexType complexType, String prefix,
Map<String, ModelProperty> propertyInfoByPath, Set<String> done) {
if (done == null) {
done = new LinkedHashSet<String>();
}
String typeName = complexType.getName();
if (done.contains(typeName)) {
log.warn("Complex type " + typeName
+ " refers to itself recursively: " + done);
// stop recursion
return;
}
done.add(typeName);
for (Field field : complexType.getFields()) {
String propertyName = field.getName().getPrefixedName(); // TODO-prefixed?
String path = prefix + propertyName;
Type fieldType = field.getType();
if (fieldType.isComplexType()) {
// complex type
inferTypePropertyPaths((ComplexType) fieldType, path + '/',
propertyInfoByPath, done);
continue;
} else if (fieldType.isListType()) {
Type listFieldType = ((ListType) fieldType).getFieldType();
if (!listFieldType.isSimpleType()) {
// complex list
inferTypePropertyPaths((ComplexType) listFieldType, path
+ "/*/", propertyInfoByPath, done);
continue;
}
// else array
} else {
// else primitive type
}
ModelProperty pi = schemaPropertyInfos.get(typeName).get(
propertyName);
propertyInfoByPath.put(path, pi);
}
done.remove(typeName);
}
/**
* Infers fulltext info for all schemas.
*/
@SuppressWarnings("unchecked")
private void inferFulltextInfo() {
List<FulltextIndexDescriptor> descs = repositoryDescriptor.fulltextIndexes;
if (descs == null) {
descs = new ArrayList<FulltextIndexDescriptor>(1);
}
if (descs.isEmpty()) {
descs.add(new FulltextIndexDescriptor());
}
for (FulltextIndexDescriptor desc : descs) {
String name = desc.name == null ? FULLTEXT_DEFAULT_INDEX
: desc.name;
fulltextInfo.indexNames.add(name);
fulltextInfo.indexAnalyzer.put(
name,
desc.analyzer == null ? repositoryDescriptor.fulltextAnalyzer
: desc.analyzer);
fulltextInfo.indexCatalog.put(name,
desc.catalog == null ? repositoryDescriptor.fulltextCatalog
: desc.catalog);
if (desc.fields == null) {
desc.fields = new HashSet<String>();
}
if (desc.excludeFields == null) {
desc.excludeFields = new HashSet<String>();
}
if (desc.fields.size() == 1 && desc.excludeFields.isEmpty()) {
fulltextInfo.fieldToIndexName.put(
desc.fields.iterator().next(), name);
}
if (desc.fieldType != null) {
if (desc.fieldType.equals(ModelFulltext.PROP_TYPE_STRING)) {
fulltextInfo.indexesAllSimple.add(name);
} else if (desc.fieldType.equals(ModelFulltext.PROP_TYPE_BLOB)) {
fulltextInfo.indexesAllBinary.add(name);
} else {
log.error("Ignoring unknow repository fulltext configuration fieldType: "
+ desc.fieldType);
}
}
if (desc.fields.isEmpty() && desc.fieldType == null) {
// no fields specified and no field type -> all of them
fulltextInfo.indexesAllSimple.add(name);
fulltextInfo.indexesAllBinary.add(name);
}
for (Set<String> fields : Arrays.asList(desc.fields,
desc.excludeFields)) {
for (String path : fields) {
ModelProperty pi = allPathPropertyInfos.get(path);
if (pi == null) {
log.error(String.format(
"Ignoring unknown property '%s' in fulltext configuration: %s",
path, name));
continue;
}
Map<String, Set<String>> indexesByPropPath;
Map<String, Set<String>> propPathsByIndex;
if (pi.propertyType == PropertyType.STRING
|| pi.propertyType == PropertyType.ARRAY_STRING) {
indexesByPropPath = fields == desc.fields ? fulltextInfo.indexesByPropPathSimple
: fulltextInfo.indexesByPropPathExcludedSimple;
propPathsByIndex = fields == desc.fields ? fulltextInfo.propPathsByIndexSimple
: fulltextInfo.propPathsExcludedByIndexSimple;
} else if (pi.propertyType == PropertyType.BINARY) {
indexesByPropPath = fields == desc.fields ? fulltextInfo.indexesByPropPathBinary
: fulltextInfo.indexesByPropPathExcludedBinary;
propPathsByIndex = fields == desc.fields ? fulltextInfo.propPathsByIndexBinary
: fulltextInfo.propPathsExcludedByIndexBinary;
} else {
log.error(String.format(
"Ignoring property '%s' with bad type %s in fulltext configuration: %s",
path, pi.propertyType, name));
continue;
}
Set<String> indexes = indexesByPropPath.get(path);
if (indexes == null) {
indexesByPropPath.put(path,
indexes = new HashSet<String>());
}
indexes.add(name);
Set<String> paths = propPathsByIndex.get(name);
if (paths == null) {
propPathsByIndex.put(name,
paths = new LinkedHashSet<String>());
}
paths.add(path);
}
}
}
}
public ModelProperty getPropertyInfo(String typeName, String propertyName) {
Map<String, ModelProperty> propertyInfos = schemaPropertyInfos.get(typeName);
if (propertyInfos == null) {
// no such type/schema
return null;
}
ModelProperty propertyInfo = propertyInfos.get(propertyName);
return propertyInfo != null ? propertyInfo
: sharedPropertyInfos.get(propertyName);
}
public Map<String, ModelProperty> getMixinPropertyInfos(String mixin) {
return mixinPropertyInfos.get(mixin);
}
public ModelProperty getMixinPropertyInfo(String mixin, String propertyName) {
Map<String, ModelProperty> propertyInfos = mixinPropertyInfos.get(mixin);
if (propertyInfos == null) {
// no such mixin
return null;
}
return propertyInfos.get(propertyName);
}
public ModelProperty getPropertyInfo(String propertyName) {
return mergedPropertyInfos.get(propertyName);
}
public Set<String> getPropertyInfoNames() {
return mergedPropertyInfos.keySet();
}
public ModelProperty getPathPropertyInfo(String primaryType,
String[] mixinTypes, String path) {
for (String schema : getAllSchemas(primaryType, mixinTypes)) {
Map<String, ModelProperty> propertyInfoByPath = schemaPathPropertyInfos.get(schema);
if (propertyInfoByPath != null) {
ModelProperty pi = propertyInfoByPath.get(path);
if (pi != null) {
return pi;
}
}
}
return null;
}
public Set<String> getSimpleTextPropertyPaths(String primaryType,
String[] mixinTypes) {
Set<String> paths = new HashSet<String>();
for (String schema : getAllSchemas(primaryType, mixinTypes)) {
Set<String> p = schemaSimpleTextPaths.get(schema);
if (p != null) {
paths.addAll(p);
}
}
return paths;
}
public Set<String> getAllSchemas(String primaryType, String[] mixinTypes) {
Set<String> schemas = new LinkedHashSet<String>();
Set<String> s = documentTypesSchemas.get(primaryType);
if (s != null) {
schemas.addAll(s);
}
for (String mixin : mixinTypes) {
s = mixinsSchemas.get(mixin);
if (s != null) {
schemas.addAll(s);
}
}
return schemas;
}
public ModelFulltext getFulltextInfo() {
return fulltextInfo;
}
/**
* Finds out if a field is to be indexed as fulltext.
*
* @param fragmentName
* @param fragmentKey the key or {@code null} for a collection
* @return {@link PropertyType#STRING} or {@link PropertyType#BINARY} if
* this field is to be indexed as fulltext
*/
public PropertyType getFulltextFieldType(String fragmentName,
String fragmentKey) {
if (fragmentKey == null) {
PropertyType type = collectionTables.get(fragmentName);
if (type == PropertyType.ARRAY_STRING
|| type == PropertyType.ARRAY_BINARY) {
return type.getArrayBaseType();
}
return null;
} else {
Map<String, ModelProperty> infos = schemaPropertyKeyInfos.get(fragmentName);
if (infos == null) {
return null;
}
ModelProperty info = infos.get(fragmentKey);
if (info != null && info.fulltext) {
return info.propertyType;
}
return null;
}
}
private void addCollectionFragmentInfos(String fragmentName,
PropertyType propertyType, String orderBy,
Map<String, ColumnType> keysType) {
collectionTables.put(fragmentName, propertyType);
collectionOrderBy.put(fragmentName, orderBy);
// set all keys types
Map<String, ColumnType> old = fragmentsKeys.get(fragmentName);
if (old == null) {
fragmentsKeys.put(fragmentName, keysType);
} else {
old.putAll(keysType);
}
}
public Type getSpecialPropertyType(String propertyName) {
return specialPropertyTypes.get(propertyName);
}
public PropertyType getCollectionFragmentType(String fragmentName) {
return collectionTables.get(fragmentName);
}
public boolean isCollectionFragment(String fragmentName) {
return collectionTables.containsKey(fragmentName);
}
public String getCollectionOrderBy(String fragmentName) {
return collectionOrderBy.get(fragmentName);
}
public Set<String> getFragmentNames() {
return fragmentsKeys.keySet();
}
public Map<String, ColumnType> getFragmentKeysType(String fragmentName) {
return fragmentsKeys.get(fragmentName);
}
protected void addTypeSimpleFragment(String typeName, String fragmentName) {
Set<String> fragments = typeSimpleFragments.get(typeName);
if (fragments == null) {
fragments = new HashSet<String>();
typeSimpleFragments.put(typeName, fragments);
}
// fragmentName may be null, to just create the entry
if (fragmentName != null) {
fragments.add(fragmentName);
}
addTypeFragment(typeName, fragmentName);
}
protected void addTypeCollectionFragment(String typeName,
String fragmentName) {
Set<String> fragments = typeCollectionFragments.get(typeName);
if (fragments == null) {
fragments = new HashSet<String>();
typeCollectionFragments.put(typeName, fragments);
}
fragments.add(fragmentName);
addTypeFragment(typeName, fragmentName);
}
protected void addTypeFragment(String typeName, String fragmentName) {
Set<String> fragments = typeFragments.get(typeName);
if (fragments == null) {
typeFragments.put(typeName, fragments = new HashSet<String>());
}
// fragmentName may be null, to just create the entry
if (fragmentName != null) {
fragments.add(fragmentName);
}
}
protected void addMixinFragment(String mixin, String fragmentName) {
Set<String> fragments = mixinFragments.get(mixin);
if (fragments == null) {
mixinFragments.put(mixin, fragments = new HashSet<String>());
}
fragments.add(fragmentName);
}
protected void addFieldFragment(Field field, String fragmentName) {
String fieldName = field.getName().toString();
Set<String> fragments = fieldFragments.get(fieldName);
if (fragments == null) {
fieldFragments.put(fieldName, fragments = new HashSet<String>());
}
fragments.add(fragmentName);
}
protected void addTypePrefetchedFragment(String typeName,
String fragmentName) {
Set<String> fragments = typePrefetchedFragments.get(typeName);
if (fragments == null) {
typePrefetchedFragments.put(typeName,
fragments = new HashSet<String>());
}
fragments.add(fragmentName);
}
public Set<String> getTypeSimpleFragments(String typeName) {
return typeSimpleFragments.get(typeName);
}
public Set<String> getTypeFragments(String typeName) {
return typeFragments.get(typeName);
}
public Set<String> getMixinFragments(String mixin) {
return mixinFragments.get(mixin);
}
protected Set<String> getFieldFragments(Field field) {
return fieldFragments.get(field.getName().toString());
}
public Set<String> getTypePrefetchedFragments(String typeName) {
return typePrefetchedFragments.get(typeName);
}
public boolean isType(String typeName) {
return typeFragments.containsKey(typeName);
}
public boolean isDocumentType(String typeName) {
return documentTypesFacets.containsKey(typeName);
}
public String getDocumentSuperType(String typeName) {
return documentSuperTypes.get(typeName);
}
public Set<String> getDocumentSubTypes(String typeName) {
return documentSubTypes.get(typeName);
}
public Set<String> getDocumentTypes() {
return documentTypesFacets.keySet();
}
public Set<String> getDocumentTypeFacets(String typeName) {
Set<String> facets = documentTypesFacets.get(typeName);
return facets == null ? Collections.<String> emptySet() : facets;
}
public Set<String> getMixinDocumentTypes(String mixin) {
Set<String> types = mixinsDocumentTypes.get(mixin);
return types == null ? Collections.<String> emptySet() : types;
}
/**
* Given a map of id to types, returns a map of fragment names to ids.
*/
public Map<String, Set<Serializable>> getPerFragmentIds(
Map<Serializable, IdWithTypes> idToTypes) {
Map<String, Set<Serializable>> allFragmentIds = new HashMap<String, Set<Serializable>>();
for (Entry<Serializable, IdWithTypes> e : idToTypes.entrySet()) {
Serializable id = e.getKey();
IdWithTypes typeInfo = e.getValue();
Set<String> fragmentNames = new HashSet<String>();
Set<String> tf = getTypeFragments(typeInfo.primaryType);
if (tf != null) {
// null if unknown type left in the database
fragmentNames.addAll(tf);
}
String[] mixins = typeInfo.mixinTypes;
if (mixins != null) {
for (String mixin : mixins) {
Set<String> mf = getMixinFragments(mixin);
if (mf != null) {
fragmentNames.addAll(mf);
}
}
}
for (String fragmentName : fragmentNames) {
Set<Serializable> fragmentIds = allFragmentIds.get(fragmentName);
if (fragmentIds == null) {
allFragmentIds.put(fragmentName,
fragmentIds = new HashSet<Serializable>());
}
fragmentIds.add(id);
}
}
return allFragmentIds;
}
private PropertyType mainIdType() {
return PropertyType.STRING;
// return PropertyType.LONG;
}
/**
* Creates all the models.
*/
private void initModels(SchemaManager schemaManager)
throws StorageException {
log.debug("Schemas fields from descriptor: "
+ repositoryDescriptor.schemaFields);
for (DocumentType documentType : schemaManager.getDocumentTypes()) {
String typeName = documentType.getName();
addTypeSimpleFragment(typeName, null); // create entry
Set<String> docTypeSchemas = new HashSet<String>();
for (Schema schema : documentType.getSchemas()) {
if (schema == null) {
// happens when a type refers to a nonexistent schema
// TODO log and avoid nulls earlier
continue;
}
docTypeSchemas.add(schema.getName());
String fragmentName = initTypeModel(schema);
addTypeSimpleFragment(typeName, fragmentName); // may be null
// collection fragments too for this schema
Set<String> cols = typeCollectionFragments.get(schema.getName());
if (cols != null) {
for (String colFrag : cols) {
addTypeCollectionFragment(typeName, colFrag);
}
}
}
documentTypesSchemas.put(typeName, docTypeSchemas);
inferTypePropertyInfos(typeName, documentType.getSchemaNames());
inferTypePropertyPaths(documentType);
for (String fragmentName : getCommonSimpleFragments()) {
addTypeSimpleFragment(typeName, fragmentName);
}
for (String fragmentName : COMMON_COLLECTION_FRAGMENTS) {
addTypeCollectionFragment(typeName, fragmentName);
}
// find fragments to prefetch
PrefetchInfo prefetch = documentType.getPrefetchInfo();
if (prefetch != null) {
Set<String> typeFragments = getTypeFragments(typeName);
for (Field field : prefetch.getFields()) {
// prefetch all the relevant fragments
Set<String> fragments = getFieldFragments(field);
if (fragments != null) {
for (String fragment : fragments) {
if (typeFragments.contains(fragment)) {
addTypePrefetchedFragment(typeName, fragment);
}
}
}
}
for (Schema schema : prefetch.getSchemas()) {
String fragment = schemaFragment.get(schema.getName());
if (fragment != null) {
addTypePrefetchedFragment(typeName, fragment);
}
Set<String> collectionFragments = typeCollectionFragments.get(typeName);
if (collectionFragments != null) {
for (String fragmentName : collectionFragments) {
addTypePrefetchedFragment(typeName, fragmentName);
}
}
}
}
// always prefetch ACLs, versions, misc (for lifecycle), locks
for (String fragmentName : ALWAYS_PREFETCHED_FRAGMENTS) {
addTypePrefetchedFragment(typeName, fragmentName);
}
log.debug("Fragments for type " + typeName + ": "
+ getTypeFragments(typeName) + ", prefetch: "
+ getTypePrefetchedFragments(typeName));
// record doc type and facets, super type, sub types
Set<String> mixins = documentType.getFacets();
documentTypesFacets.put(typeName, new HashSet<String>(mixins));
for (String mixin : mixins) {
Set<String> mixinTypes = mixinsDocumentTypes.get(mixin);
if (mixinTypes == null) {
mixinsDocumentTypes.put(mixin,
mixinTypes = new HashSet<String>());
}
mixinTypes.add(typeName);
}
Type superType = documentType.getSuperType();
if (superType != null) {
String superTypeName = superType.getName();
documentSuperTypes.put(typeName, superTypeName);
}
}
// compute subtypes for all types
for (String type : getDocumentTypes()) {
String superType = type;
do {
Set<String> subTypes = documentSubTypes.get(superType);
if (subTypes == null) {
subTypes = new HashSet<String>();
documentSubTypes.put(superType, subTypes);
}
subTypes.add(type);
superType = documentSuperTypes.get(superType);
} while (superType != null);
}
if (!repositoryDescriptor.fulltextDisabled) {
// infer fulltext info
inferFulltextInfo();
}
// mixins
for (CompositeType type : schemaManager.getFacets()) {
String mixin = type.getName();
// some schemas may be referenced only by mixins,
// register them here
Set<String> mixinSchemas = new HashSet<String>();
for (Schema schema : type.getSchemas()) {
if (schema == null) {
// happens when a type refers to a nonexistent schema
// TODO log and avoid nulls earlier
continue;
}
mixinSchemas.add(schema.getName());
String fragmentName = initTypeModel(schema);
addMixinFragment(mixin, fragmentName);
inferSchemaPropertyPaths(schema);
}
mixinsSchemas.put(mixin, mixinSchemas);
inferMixinPropertyInfos(mixin, type.getSchemaNames());
log.debug("Fragments for facet " + mixin + ": "
+ getMixinFragments(mixin));
}
}
protected List<String> getCommonSimpleFragments() {
List<String> fragments = COMMON_SIMPLE_FRAGMENTS;
if (!repositoryDescriptor.fulltextDisabled) {
fragments = new ArrayList<String>(fragments);
fragments.add(FULLTEXT_TABLE_NAME);
}
return fragments;
}
/**
* Special model for the main table (the one containing the primary type
* information).
* <p>
* If the main table is not separate from the hierarchy table, then it's
* will not really be instantiated by itself but merged into the hierarchy
* table.
*/
private void initMainModel() {
addPropertyInfo(null, MAIN_PRIMARY_TYPE_PROP, PropertyType.STRING,
HIER_TABLE_NAME, MAIN_PRIMARY_TYPE_KEY, true, null,
ColumnType.SYSNAME);
addPropertyInfo(null, MAIN_MIXIN_TYPES_PROP, PropertyType.STRING,
HIER_TABLE_NAME, MAIN_MIXIN_TYPES_KEY, false, null,
ColumnType.SYSNAMEARRAY);
addPropertyInfo(null, MAIN_CHECKED_IN_PROP, PropertyType.BOOLEAN,
HIER_TABLE_NAME, MAIN_CHECKED_IN_KEY, false,
BooleanType.INSTANCE, ColumnType.BOOLEAN);
addPropertyInfo(null, MAIN_BASE_VERSION_PROP, mainIdType(),
HIER_TABLE_NAME, MAIN_BASE_VERSION_KEY, false,
StringType.INSTANCE, ColumnType.NODEVAL);
addPropertyInfo(null, MAIN_MAJOR_VERSION_PROP, PropertyType.LONG,
HIER_TABLE_NAME, MAIN_MAJOR_VERSION_KEY, false,
LongType.INSTANCE, ColumnType.INTEGER);
addPropertyInfo(null, MAIN_MINOR_VERSION_PROP, PropertyType.LONG,
HIER_TABLE_NAME, MAIN_MINOR_VERSION_KEY, false,
LongType.INSTANCE, ColumnType.INTEGER);
addPropertyInfo(null, MAIN_IS_VERSION_PROP, PropertyType.BOOLEAN,
HIER_TABLE_NAME, MAIN_IS_VERSION_KEY, false,
BooleanType.INSTANCE, ColumnType.BOOLEAN);
}
/**
* Special model for the "misc" table (lifecycle, dirty.).
*/
private void initMiscModel() {
addPropertyInfo(null, MISC_LIFECYCLE_POLICY_PROP, PropertyType.STRING,
MISC_TABLE_NAME, MISC_LIFECYCLE_POLICY_KEY, false,
StringType.INSTANCE, ColumnType.SYSNAME);
addPropertyInfo(null, MISC_LIFECYCLE_STATE_PROP, PropertyType.STRING,
MISC_TABLE_NAME, MISC_LIFECYCLE_STATE_KEY, false,
StringType.INSTANCE, ColumnType.SYSNAME);
}
/**
* Special model for the versions table.
*/
private void initVersionsModel() {
addPropertyInfo(null, VERSION_VERSIONABLE_PROP, mainIdType(),
VERSION_TABLE_NAME, VERSION_VERSIONABLE_KEY, false,
StringType.INSTANCE, ColumnType.NODEVAL);
addPropertyInfo(null, VERSION_CREATED_PROP, PropertyType.DATETIME,
VERSION_TABLE_NAME, VERSION_CREATED_KEY, false,
DateType.INSTANCE, ColumnType.TIMESTAMP);
addPropertyInfo(null, VERSION_LABEL_PROP, PropertyType.STRING,
VERSION_TABLE_NAME, VERSION_LABEL_KEY, false,
StringType.INSTANCE, ColumnType.SYSNAME);
addPropertyInfo(null, VERSION_DESCRIPTION_PROP, PropertyType.STRING,
VERSION_TABLE_NAME, VERSION_DESCRIPTION_KEY, false,
StringType.INSTANCE, ColumnType.STRING);
addPropertyInfo(null, VERSION_IS_LATEST_PROP, PropertyType.BOOLEAN,
VERSION_TABLE_NAME, VERSION_IS_LATEST_KEY, false,
BooleanType.INSTANCE, ColumnType.BOOLEAN);
addPropertyInfo(null, VERSION_IS_LATEST_MAJOR_PROP,
PropertyType.BOOLEAN, VERSION_TABLE_NAME,
VERSION_IS_LATEST_MAJOR_KEY, false, BooleanType.INSTANCE,
ColumnType.BOOLEAN);
}
/**
* Special model for the proxies table.
*/
private void initProxiesModel() {
addPropertyInfo(PROXY_TYPE, PROXY_TARGET_PROP, mainIdType(),
PROXY_TABLE_NAME, PROXY_TARGET_KEY, false, StringType.INSTANCE,
ColumnType.NODEIDFKNP);
addPropertyInfo(PROXY_TYPE, PROXY_VERSIONABLE_PROP, mainIdType(),
PROXY_TABLE_NAME, PROXY_VERSIONABLE_KEY, false,
StringType.INSTANCE, ColumnType.NODEVAL);
addTypeSimpleFragment(PROXY_TYPE, PROXY_TABLE_NAME);
}
/**
* Special model for the locks table (also, primary key has no foreign key,
* see {@link SQLInfo#initFragmentSQL}.
*/
private void initLocksModel() {
addPropertyInfo(null, LOCK_OWNER_PROP, PropertyType.STRING,
LOCK_TABLE_NAME, LOCK_OWNER_KEY, false, StringType.INSTANCE,
ColumnType.SYSNAME);
addPropertyInfo(null, LOCK_CREATED_PROP, PropertyType.DATETIME,
LOCK_TABLE_NAME, LOCK_CREATED_KEY, false, DateType.INSTANCE,
ColumnType.TIMESTAMP);
}
/**
* Special model for the fulltext table.
*/
private void initFullTextModel() {
addPropertyInfo(null, FULLTEXT_JOBID_PROP, PropertyType.STRING,
FULLTEXT_TABLE_NAME, FULLTEXT_JOBID_KEY, false,
StringType.INSTANCE, ColumnType.SYSNAME);
for (String indexName : fulltextInfo.indexNames) {
String suffix = getFulltextIndexSuffix(indexName);
if (materializeFulltextSyntheticColumn) {
addPropertyInfo(null, FULLTEXT_FULLTEXT_PROP + suffix,
PropertyType.STRING, FULLTEXT_TABLE_NAME,
FULLTEXT_FULLTEXT_KEY + suffix, false,
StringType.INSTANCE, ColumnType.FTINDEXED);
}
addPropertyInfo(null, FULLTEXT_SIMPLETEXT_PROP + suffix,
PropertyType.STRING, FULLTEXT_TABLE_NAME,
FULLTEXT_SIMPLETEXT_KEY + suffix, false,
StringType.INSTANCE, ColumnType.FTSTORED);
addPropertyInfo(null, FULLTEXT_BINARYTEXT_PROP + suffix,
PropertyType.STRING, FULLTEXT_TABLE_NAME,
FULLTEXT_BINARYTEXT_KEY + suffix, false,
StringType.INSTANCE, ColumnType.FTSTORED);
}
}
public String getFulltextIndexSuffix(String indexName) {
return indexName.equals(FULLTEXT_DEFAULT_INDEX) ? "" : '_' + indexName;
}
/**
* Special collection-like model for the ACL table.
*/
private void initAclModel() {
Map<String, ColumnType> keysType = new LinkedHashMap<String, ColumnType>();
keysType.put(ACL_POS_KEY, ColumnType.INTEGER);
keysType.put(ACL_NAME_KEY, ColumnType.SYSNAME);
keysType.put(ACL_GRANT_KEY, ColumnType.BOOLEAN);
keysType.put(ACL_PERMISSION_KEY, ColumnType.SYSNAME);
keysType.put(ACL_USER_KEY, ColumnType.SYSNAME);
keysType.put(ACL_GROUP_KEY, ColumnType.SYSNAME);
String fragmentName = ACL_TABLE_NAME;
addCollectionFragmentInfos(fragmentName, PropertyType.COLL_ACL,
ACL_POS_KEY, keysType);
addPropertyInfo(null, ACL_PROP, PropertyType.COLL_ACL, fragmentName,
null, false, null, null);
}
/**
* Creates the model for one schema or complex type.
*
* @return the fragment table name for this type, or {@code null} if this
* type doesn't directly hold data
*/
private String initTypeModel(ComplexType complexType)
throws StorageException {
String typeName = complexType.getName();
if (schemaFragment.containsKey(typeName)) {
return schemaFragment.get(typeName); // may be null
}
log.debug("Making model for type " + typeName);
/** Initialized if this type has a table associated. */
String thisFragmentName = null;
for (Field field : complexType.getFields()) {
Type fieldType = field.getType();
if (fieldType.isComplexType()) {
/*
* Complex type.
*/
ComplexType fieldComplexType = (ComplexType) fieldType;
String subTypeName = fieldComplexType.getName();
String subFragmentName = initTypeModel(fieldComplexType);
addTypeSimpleFragment(subTypeName, subFragmentName);
} else {
String propertyName = field.getName().getPrefixedName();
if (fieldType.isListType()) {
Type listFieldType = ((ListType) fieldType).getFieldType();
if (listFieldType.isSimpleType()) {
/*
* Array: use a collection table.
*/
String fragmentName = collectionFragmentName(propertyName);
PropertyType propertyType = PropertyType.fromFieldType(
listFieldType, true);
ColumnType type = ColumnType.fromFieldType(listFieldType);
if (type.spec == ColumnSpec.STRING) {
for (FieldDescriptor fd : repositoryDescriptor.schemaFields) {
if (propertyName.equals(fd.field)
&& FIELD_TYPE_LARGETEXT.equals(fd.type)) {
type = ColumnType.CLOB;
break;
}
}
log.debug(" String array field '" + propertyName
+ "' using column type " + type);
}
addPropertyInfo(typeName, propertyName, propertyType,
fragmentName, null, false, null, null);
Map<String, ColumnType> keysType = new LinkedHashMap<String, ColumnType>();
keysType.put(COLL_TABLE_POS_KEY, ColumnType.INTEGER);
keysType.put(COLL_TABLE_VALUE_KEY, type);
addCollectionFragmentInfos(fragmentName, propertyType,
COLL_TABLE_POS_KEY, keysType);
addTypeCollectionFragment(typeName, fragmentName);
addFieldFragment(field, fragmentName);
} else {
/*
* Complex list.
*/
String subFragmentName = initTypeModel((ComplexType) listFieldType);
addTypeSimpleFragment(listFieldType.getName(),
subFragmentName);
}
} else {
/*
* Primitive type.
*/
String fragmentName = typeFragmentName(complexType);
PropertyType propertyType = PropertyType.fromFieldType(
fieldType, false);
ColumnType type = ColumnType.fromField(field);
if (type.spec == ColumnSpec.STRING) {
// backward compat with largetext, since 5.4.2
for (FieldDescriptor fd : repositoryDescriptor.schemaFields) {
if (propertyName.equals(fd.field)
&& FIELD_TYPE_LARGETEXT.equals(fd.type)) {
if (!type.isUnconstrained() && !type.isClob()) {
log.warn(" String field '" + propertyName
+ "' has a schema constraint to "
+ type
+ " but is specified as largetext,"
+ " using CLOB for it");
}
type = ColumnType.CLOB;
break;
}
}
}
String fragmentKey = field.getName().getLocalName();
if (MAIN_KEY.equalsIgnoreCase(fragmentKey)) {
String msg = "A property cannot be named '"
+ fragmentKey
+ "' because this is a reserved name, in type: "
+ typeName;
throw new StorageException(msg);
}
if (fragmentName.equals(UID_SCHEMA_NAME)
&& (fragmentKey.equals(UID_MAJOR_VERSION_KEY) || fragmentKey.equals(UID_MINOR_VERSION_KEY))) {
// workaround: special-case the "uid" schema, put major/minor
// in the hierarchy table
fragmentKey = fragmentKey.equals(UID_MAJOR_VERSION_KEY) ? MAIN_MAJOR_VERSION_KEY
: MAIN_MINOR_VERSION_KEY;
addPropertyInfo(typeName, propertyName, propertyType,
HIER_TABLE_NAME, fragmentKey, false, null, type);
} else {
addPropertyInfo(typeName, propertyName, propertyType,
fragmentName, fragmentKey, false, null, type);
// note that this type has a fragment
thisFragmentName = fragmentName;
}
addFieldFragment(field, fragmentName);
}
}
}
schemaFragment.put(typeName, thisFragmentName); // may be null
return thisFragmentName;
}
private static String typeFragmentName(ComplexType type) {
return type.getName();
}
private String collectionFragmentName(String propertyName) {
return propertyName;
}
}