| /** |
| * Copyright (c) 2004 - 2011 Eike Stepper (Berlin, Germany) 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: |
| * Stefan Winkler - initial API and implementation |
| * Stefan Winkler - Bug 285426: [DB] Implement user-defined typeMapping support |
| */ |
| package org.eclipse.emf.cdo.server.internal.db.mapping; |
| |
| import org.eclipse.emf.cdo.common.model.CDOModelUtil; |
| import org.eclipse.emf.cdo.etypes.EtypesPackage; |
| import org.eclipse.emf.cdo.server.db.IIDHandler; |
| import org.eclipse.emf.cdo.server.db.mapping.IMappingStrategy; |
| import org.eclipse.emf.cdo.server.db.mapping.ITypeMapping; |
| import org.eclipse.emf.cdo.server.internal.db.DBAnnotation; |
| import org.eclipse.emf.cdo.server.internal.db.bundle.OM; |
| import org.eclipse.emf.cdo.server.internal.db.mapping.TypeMappingUtil.FactoryTypeParserException; |
| import org.eclipse.emf.cdo.server.internal.db.messages.Messages; |
| |
| import org.eclipse.net4j.db.DBType; |
| import org.eclipse.net4j.db.IDBAdapter; |
| import org.eclipse.net4j.util.collection.Pair; |
| import org.eclipse.net4j.util.container.ContainerEvent; |
| import org.eclipse.net4j.util.container.IContainerDelta; |
| import org.eclipse.net4j.util.container.IContainerDelta.Kind; |
| import org.eclipse.net4j.util.container.IManagedContainer; |
| import org.eclipse.net4j.util.container.IPluginContainer; |
| import org.eclipse.net4j.util.event.IEvent; |
| import org.eclipse.net4j.util.event.IListener; |
| import org.eclipse.net4j.util.factory.IFactory; |
| import org.eclipse.net4j.util.factory.IFactoryKey; |
| import org.eclipse.net4j.util.om.trace.ContextTracer; |
| |
| import org.eclipse.emf.ecore.EClass; |
| import org.eclipse.emf.ecore.EClassifier; |
| import org.eclipse.emf.ecore.EEnum; |
| import org.eclipse.emf.ecore.EPackage; |
| import org.eclipse.emf.ecore.EReference; |
| import org.eclipse.emf.ecore.EStructuralFeature; |
| import org.eclipse.emf.ecore.EcorePackage; |
| |
| import java.text.MessageFormat; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| |
| /** |
| * An implementation of both the Registry and Provider interfaces for type mappings. This class is a singleton which |
| * keeps itself in sync with the global factory registry. It reads the available factoryTypes for the type mappings |
| * product type and populates indexes which make it easier to determine and look up the correct factories for a needed |
| * type mapping. |
| * |
| * @author Stefan Winkler |
| */ |
| public class TypeMappingRegistry implements ITypeMapping.Registry, ITypeMapping.Provider |
| { |
| private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG, TypeMappingRegistry.class); |
| |
| /** |
| * Contains a map from model types to db types which represent default mappings. (I.e., if a model element without db |
| * type annotation is encountered, this map is consulted to retrieve the default type mapping. This map is populated |
| * on a come-first basis. The first mapping for a particular {@link EClassifier} is set as default. |
| */ |
| private Map<EClassifier, DBType> classifierDefaultMapping; |
| |
| /** |
| * The main TypeMapping index. For any known pair of model and db types the {@link ITypeMapping.Descriptor} is |
| * registered here. |
| */ |
| private Map<Pair<EClassifier, DBType>, ITypeMapping.Descriptor> typeMappingByTypes; |
| |
| /** |
| * ID-based index. Can be used to lookup an {@link ITypeMapping.Descriptor} for a given ID. |
| */ |
| private Map<String, ITypeMapping.Descriptor> typeMappingsById; |
| |
| /** |
| * A set of all known mapped DBTypes. This is needed for the feature map mappings. |
| */ |
| private Set<DBType> defaultFeatureMapDBTypes; |
| |
| /** |
| * A populator which is used to keep the registry in sync with the registered factories of the |
| * {@link IManagedContainer}. |
| */ |
| private RegistryPopulator populator = new RegistryPopulator(); |
| |
| public TypeMappingRegistry() |
| { |
| init(); |
| } |
| |
| public void init() |
| { |
| populator.disconnect(); |
| |
| defaultFeatureMapDBTypes = new HashSet<DBType>(); |
| typeMappingsById = new HashMap<String, ITypeMapping.Descriptor>(); |
| typeMappingByTypes = new HashMap<Pair<EClassifier, DBType>, ITypeMapping.Descriptor>(); |
| classifierDefaultMapping = new HashMap<EClassifier, DBType>(); |
| |
| registerCoreTypeMappings(); |
| populator.connect(); |
| } |
| |
| /** |
| * Register builtin type mapping factories |
| */ |
| private void registerCoreTypeMappings() |
| { |
| IManagedContainer container = getContainer(); |
| container.registerFactory(CoreTypeMappings.TMBigDecimal.FACTORY); |
| container.registerFactory(CoreTypeMappings.TMBigDecimal.FACTORY_LONG_VARCHAR); |
| container.registerFactory(CoreTypeMappings.TMBigInteger.FACTORY); |
| container.registerFactory(CoreTypeMappings.TMBigInteger.FACTORY_LONG_VARCHAR); |
| container.registerFactory(CoreTypeMappings.TMBoolean.FACTORY); |
| container.registerFactory(CoreTypeMappings.TMBoolean.FACTORY_SMALLINT); |
| container.registerFactory(CoreTypeMappings.TMBoolean.FACTORY_OBJECT); |
| container.registerFactory(CoreTypeMappings.TMBoolean.FACTORY_OBJECT_SMALLINT); |
| container.registerFactory(CoreTypeMappings.TMByte.FACTORY); |
| container.registerFactory(CoreTypeMappings.TMByte.FACTORY_OBJECT); |
| container.registerFactory(CoreTypeMappings.TMBytes.FACTORY); |
| container.registerFactory(CoreTypeMappings.TMCharacter.FACTORY); |
| container.registerFactory(CoreTypeMappings.TMCharacter.FACTORY_OBJECT); |
| container.registerFactory(CoreTypeMappings.TMCustom.FACTORY_VARCHAR); |
| container.registerFactory(CoreTypeMappings.TMCustom.FACTORY_CLOB); |
| container.registerFactory(CoreTypeMappings.TMCustom.FACTORY_LONG_VARCHAR); |
| container.registerFactory(CoreTypeMappings.TMDate2Date.FACTORY); |
| container.registerFactory(CoreTypeMappings.TMDate2Time.FACTORY); |
| container.registerFactory(CoreTypeMappings.TMDate2Timestamp.FACTORY); |
| container.registerFactory(CoreTypeMappings.TMDouble.FACTORY); |
| container.registerFactory(CoreTypeMappings.TMDouble.FACTORY_OBJECT); |
| container.registerFactory(CoreTypeMappings.TMEnum.FACTORY); |
| container.registerFactory(CoreTypeMappings.TMFloat.FACTORY); |
| container.registerFactory(CoreTypeMappings.TMFloat.FACTORY_OBJECT); |
| container.registerFactory(CoreTypeMappings.TMInteger.FACTORY); |
| container.registerFactory(CoreTypeMappings.TMInteger.FACTORY_OBJECT); |
| container.registerFactory(CoreTypeMappings.TMLong.FACTORY); |
| container.registerFactory(CoreTypeMappings.TMLong.FACTORY_OBJECT); |
| container.registerFactory(CoreTypeMappings.TMShort.FACTORY); |
| container.registerFactory(CoreTypeMappings.TMShort.FACTORY_OBJECT); |
| container.registerFactory(CoreTypeMappings.TMString.FACTORY_VARCHAR); |
| container.registerFactory(CoreTypeMappings.TMString.FACTORY_CLOB); |
| container.registerFactory(CoreTypeMappings.TMString.FACTORY_LONG_VARCHAR); |
| container.registerFactory(CoreTypeMappings.TMBlob.FACTORY_VARCHAR); |
| container.registerFactory(CoreTypeMappings.TMBlob.FACTORY_LONG_VARCHAR); |
| container.registerFactory(CoreTypeMappings.TMClob.FACTORY_VARCHAR); |
| container.registerFactory(CoreTypeMappings.TMClob.FACTORY_LONG_VARCHAR); |
| |
| classifierDefaultMapping.put(EcorePackage.eINSTANCE.getEDataType(), DBType.VARCHAR); |
| |
| classifierDefaultMapping.put(EcorePackage.eINSTANCE.getEBigDecimal(), DBType.VARCHAR); |
| classifierDefaultMapping.put(EcorePackage.eINSTANCE.getEBigInteger(), DBType.VARCHAR); |
| classifierDefaultMapping.put(EcorePackage.eINSTANCE.getEBoolean(), DBType.BOOLEAN); |
| classifierDefaultMapping.put(EcorePackage.eINSTANCE.getEBooleanObject(), DBType.BOOLEAN); |
| classifierDefaultMapping.put(EcorePackage.eINSTANCE.getEByte(), DBType.SMALLINT); |
| classifierDefaultMapping.put(EcorePackage.eINSTANCE.getEByteObject(), DBType.SMALLINT); |
| classifierDefaultMapping.put(EcorePackage.eINSTANCE.getEByteArray(), DBType.BLOB); |
| classifierDefaultMapping.put(EcorePackage.eINSTANCE.getEChar(), DBType.CHAR); |
| classifierDefaultMapping.put(EcorePackage.eINSTANCE.getECharacterObject(), DBType.CHAR); |
| classifierDefaultMapping.put(EcorePackage.eINSTANCE.getEDate(), DBType.TIMESTAMP); |
| classifierDefaultMapping.put(EcorePackage.eINSTANCE.getEDouble(), DBType.DOUBLE); |
| classifierDefaultMapping.put(EcorePackage.eINSTANCE.getEDoubleObject(), DBType.DOUBLE); |
| classifierDefaultMapping.put(EcorePackage.eINSTANCE.getEEnum(), DBType.INTEGER); |
| classifierDefaultMapping.put(EcorePackage.eINSTANCE.getEFloat(), DBType.FLOAT); |
| classifierDefaultMapping.put(EcorePackage.eINSTANCE.getEFloatObject(), DBType.FLOAT); |
| classifierDefaultMapping.put(EcorePackage.eINSTANCE.getEInt(), DBType.INTEGER); |
| classifierDefaultMapping.put(EcorePackage.eINSTANCE.getEIntegerObject(), DBType.INTEGER); |
| classifierDefaultMapping.put(EcorePackage.eINSTANCE.getELong(), DBType.BIGINT); |
| classifierDefaultMapping.put(EcorePackage.eINSTANCE.getELongObject(), DBType.BIGINT); |
| classifierDefaultMapping.put(EcorePackage.eINSTANCE.getEShort(), DBType.SMALLINT); |
| classifierDefaultMapping.put(EcorePackage.eINSTANCE.getEShortObject(), DBType.SMALLINT); |
| classifierDefaultMapping.put(EcorePackage.eINSTANCE.getEString(), DBType.VARCHAR); |
| |
| classifierDefaultMapping.put(EtypesPackage.eINSTANCE.getBlob(), DBType.VARCHAR); // TODO Should be DBType.BLOB? |
| classifierDefaultMapping.put(EtypesPackage.eINSTANCE.getClob(), DBType.VARCHAR); // TODO Should be DBType.CLOB? |
| } |
| |
| protected IManagedContainer getContainer() |
| { |
| return IPluginContainer.INSTANCE; |
| } |
| |
| public void registerTypeMapping(ITypeMapping.Descriptor descriptor) |
| { |
| if (TRACER.isEnabled()) |
| { |
| TRACER.format("Registering {0}", descriptor); |
| } |
| |
| EClassifier eClassifier = descriptor.getEClassifier(); |
| DBType dbType = descriptor.getDBType(); |
| Pair<EClassifier, DBType> sourceTargetPair = new Pair<EClassifier, DBType>(eClassifier, dbType); |
| |
| // currently we do not support more than one typeMapping per source-target type pair |
| if (typeMappingByTypes.containsKey(sourceTargetPair)) |
| { |
| OM.LOG.error(Messages.getString("TypeMappingRegistry.4")); |
| return; |
| } |
| |
| if (typeMappingsById.containsKey(descriptor.getID())) |
| { |
| OM.LOG.error(MessageFormat.format(Messages.getString("TypeMappingRegistry.5"), descriptor.getID())); |
| return; |
| } |
| |
| typeMappingsById.put(descriptor.getID(), descriptor); |
| |
| // register first dbType for classifier as default |
| if (!classifierDefaultMapping.containsKey(eClassifier)) |
| { |
| classifierDefaultMapping.put(eClassifier, dbType); |
| } |
| |
| defaultFeatureMapDBTypes.add(dbType); |
| |
| typeMappingByTypes.put(sourceTargetPair, descriptor); |
| } |
| |
| public ITypeMapping createTypeMapping(IMappingStrategy mappingStrategy, EStructuralFeature feature) |
| { |
| ITypeMapping typeMapping = null; |
| if (feature instanceof EReference) |
| { |
| IIDHandler idHandler = mappingStrategy.getStore().getIDHandler(); |
| typeMapping = idHandler.getObjectTypeMapping(); |
| typeMapping.setDBType(idHandler.getDBType()); |
| } |
| else |
| { |
| IDBAdapter dbAdapter = mappingStrategy.getStore().getDBAdapter(); |
| DBType dbType = getDBType(feature, dbAdapter); |
| |
| ITypeMapping.Descriptor descriptor = null; |
| |
| String typeMappingID = DBAnnotation.TYPE_MAPPING.getValue(feature); |
| if (typeMappingID != null) |
| { |
| // lookup annotated mapping |
| descriptor = typeMappingsById.get(typeMappingID); |
| |
| if (descriptor == null) |
| { |
| OM.LOG.warn(MessageFormat.format(Messages.getString("TypeMappingRegistry.2"), // |
| typeMappingID, feature.toString())); |
| } |
| } |
| |
| if (descriptor == null) |
| { |
| // try to find suitable mapping by type |
| descriptor = getMappingByType(feature, dbType); |
| } |
| |
| if (descriptor == null) |
| { |
| EClassifier type = getEType(feature); |
| throw new IllegalStateException(MessageFormat.format(Messages.getString("TypeMappingRegistry.1"), feature |
| .getEContainingClass().getName() + "." + feature.getName(), |
| type.getEPackage().getName() + "." + type.getName(), dbType.getKeyword())); |
| } |
| |
| IFactory factory = getContainer().getFactory(ITypeMapping.Factory.PRODUCT_GROUP, descriptor.getFactoryType()); |
| typeMapping = (ITypeMapping)factory.create(null); |
| typeMapping.setDBType(dbType); |
| } |
| |
| typeMapping.setMappingStrategy(mappingStrategy); |
| typeMapping.setFeature(feature); |
| return typeMapping; |
| } |
| |
| private EClassifier getEType(EStructuralFeature feature) |
| { |
| EClassifier classifier = feature.getEType(); |
| if (classifier instanceof EEnum) |
| { |
| return EcorePackage.eINSTANCE.getEEnum(); |
| } |
| |
| if (classifier instanceof EClass) |
| { |
| return EcorePackage.eINSTANCE.getEClass(); |
| } |
| |
| EPackage ePackage = classifier.getEPackage(); |
| if (CDOModelUtil.isCorePackage(ePackage)) |
| { |
| return classifier; |
| } |
| |
| if (CDOModelUtil.isTypesPackage(ePackage)) |
| { |
| return classifier; |
| } |
| |
| return EcorePackage.eINSTANCE.getEDataType(); |
| } |
| |
| private DBType getDBType(EStructuralFeature feature, IDBAdapter dbAdapter) |
| { |
| String typeKeyword = DBAnnotation.COLUMN_TYPE.getValue(feature); |
| if (typeKeyword != null) |
| { |
| DBType dbType = DBType.getTypeByKeyword(typeKeyword); |
| if (dbType == null) |
| { |
| throw new IllegalArgumentException("Unsupported columnType (" + typeKeyword + ") annotation of feature " |
| + feature.getName()); |
| } |
| |
| return dbType; |
| } |
| |
| // No annotation present - lookup default DB type. |
| return getDefaultDBType(getEType(feature), dbAdapter); |
| } |
| |
| private DBType getDefaultDBType(EClassifier type, IDBAdapter dbAdapter) |
| { |
| DBType result = classifierDefaultMapping.get(type); |
| |
| if (result == null) |
| { |
| result = DBType.VARCHAR; |
| } |
| |
| // Give the DBAdapter a chance to override the default type, if it's not supported |
| return dbAdapter.adaptType(result); |
| } |
| |
| private ITypeMapping.Descriptor getMappingByType(EStructuralFeature feature, DBType dbType) |
| { |
| // First try: lookup specific mapping for the immediate type. |
| ITypeMapping.Descriptor descriptor = typeMappingByTypes.get(new Pair<EClassifier, DBType>(feature.getEType(), |
| dbType)); |
| |
| if (descriptor == null) |
| { |
| // Second try: lookup general mapping |
| descriptor = typeMappingByTypes.get(new Pair<EClassifier, DBType>(getEType(feature), dbType)); |
| if (descriptor == null) |
| { |
| // Lookup failed. Give up |
| return null; |
| } |
| } |
| |
| return descriptor; |
| } |
| |
| public Collection<DBType> getDefaultFeatureMapDBTypes() |
| { |
| return defaultFeatureMapDBTypes; |
| } |
| |
| /** |
| * Keeps the {@link TypeMappingRegistry} in sync with {@link IManagedContainer#getFactoryRegistry()}. |
| * |
| * @author Stefan Winkler |
| */ |
| private class RegistryPopulator implements IListener |
| { |
| private IManagedContainer container = getContainer(); |
| |
| public RegistryPopulator() |
| { |
| } |
| |
| /** |
| * Connect to the factory registry. |
| */ |
| public void connect() |
| { |
| populateTypeMappingRegistry(); |
| container.getFactoryRegistry().addListener(this); |
| } |
| |
| public void disconnect() |
| { |
| container.getFactoryRegistry().removeListener(this); |
| } |
| |
| private void populateTypeMappingRegistry() |
| { |
| // get available factory types |
| Set<String> factoryTypes = container.getFactoryTypes(ITypeMapping.Factory.PRODUCT_GROUP); |
| |
| // parse the descriptor of each factory type |
| for (String factoryType : factoryTypes) |
| { |
| registerFactoryType(factoryType); |
| } |
| } |
| |
| private void registerFactoryType(String factoryType) |
| { |
| ITypeMapping.Descriptor desc; |
| |
| try |
| { |
| desc = TypeMappingUtil.descriptorFromFactoryType(factoryType); |
| registerTypeMapping(desc); |
| } |
| catch (FactoryTypeParserException ex) |
| { |
| OM.LOG.warn(ex); |
| } |
| } |
| |
| public void notifyEvent(IEvent event) |
| { |
| if (event instanceof ContainerEvent<?>) |
| { |
| @SuppressWarnings("unchecked") |
| ContainerEvent<Map.Entry<IFactoryKey, IFactory>> ev = (ContainerEvent<Entry<IFactoryKey, IFactory>>)event; |
| |
| for (IContainerDelta<Map.Entry<IFactoryKey, IFactory>> delta : ev.getDeltas()) |
| { |
| IFactoryKey key = delta.getElement().getKey(); |
| if (key.getProductGroup().equals(ITypeMapping.Factory.PRODUCT_GROUP)) |
| { |
| if (delta.getKind() == Kind.ADDED) |
| { |
| String factoryType = delta.getElement().getKey().getType(); |
| registerFactoryType(factoryType); |
| } |
| else |
| // delta.getKind() == Kind.REMOVED |
| { |
| // XXX Runtime removal of typeMappingFactories removal of type mappings is currently not supported. |
| OM.LOG.warn(Messages.getString("TypeMappingRegistry.3")); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |