[417741] [DB] Add support for database index creation with DBAnnotation

https://bugs.eclipse.org/bugs/show_bug.cgi?id=417741
diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBIndexAnnotation.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBIndexAnnotation.java
new file mode 100644
index 0000000..41ec7d2
--- /dev/null
+++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBIndexAnnotation.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2016 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:
+ *    Eike Stepper - initial API and implementation
+ */
+package org.eclipse.emf.cdo.server.internal.db;
+
+import org.eclipse.emf.cdo.server.internal.db.bundle.OM;
+
+import org.eclipse.emf.common.util.BasicEList;
+import org.eclipse.emf.common.util.EList;
+import org.eclipse.emf.ecore.EAnnotation;
+import org.eclipse.emf.ecore.EClass;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EStructuralFeature;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+/**
+ * @author Kai Schlamp
+ */
+public final class DBIndexAnnotation
+{
+  public static final String SOURCE_URI = "http://www.eclipse.org/CDO/DBIndex";
+
+  public static final String FEATURES = "features";
+
+  private DBIndexAnnotation()
+  {
+  }
+
+  public static Set<List<EStructuralFeature>> getIndices(EClass eClass, EStructuralFeature[] allPersistentFeatures)
+  {
+    Set<List<EStructuralFeature>> indices = new HashSet<List<EStructuralFeature>>();
+
+    for (EAnnotation annotation : getAnnotations(eClass))
+    {
+      List<EStructuralFeature> features = new ArrayList<EStructuralFeature>();
+
+      String featureNames = annotation.getDetails().get(FEATURES);
+      if (featureNames != null && featureNames.length() != 0)
+      {
+        StringTokenizer tokenizer = new StringTokenizer(featureNames, ",");
+        while (tokenizer.hasMoreTokens())
+        {
+          String featureName = tokenizer.nextToken().trim();
+          if (featureName.length() != 0)
+          {
+            EStructuralFeature feature = getPersistentFeature(featureName, allPersistentFeatures);
+            if (feature == null)
+            {
+              OM.LOG.warn("Feature '" + featureName + "' not found in class '" + eClass.getName() + "' in package '" + eClass.getEPackage().getNsURI() + "'");
+              continue;
+            }
+
+            features.add(feature);
+          }
+        }
+      }
+      else
+      {
+        for (EObject reference : annotation.getReferences())
+        {
+          if (reference instanceof EStructuralFeature)
+          {
+            EStructuralFeature feature = (EStructuralFeature)reference;
+            if (!isPersistentFeature(feature, allPersistentFeatures))
+            {
+              OM.LOG.warn("Feature '" + feature.getName() + "' is not a persistent feature of class '" + eClass.getName() + "' in package '"
+                  + eClass.getEPackage().getNsURI() + "'");
+              continue;
+            }
+
+            features.add(feature);
+          }
+          else
+          {
+            OM.LOG.warn("Reference '" + reference + "' is not a feature");
+          }
+        }
+      }
+
+      int size = features.size();
+      if (size > 0)
+      {
+        if (size > 1)
+        {
+          for (EStructuralFeature feature : features)
+          {
+            if (feature.isMany())
+            {
+              OM.LOG.warn("Many-valued feature '" + feature.getName() + "' not allowed in composed index on class '" + eClass.getName() + "' in package '"
+                  + eClass.getEPackage().getNsURI() + "'");
+              continue;
+            }
+          }
+        }
+
+        indices.add(features);
+      }
+    }
+
+    for (EStructuralFeature feature : allPersistentFeatures)
+    {
+      if (feature.getEAnnotation(SOURCE_URI) != null)
+      {
+        indices.add(Collections.singletonList(feature));
+      }
+    }
+
+    return indices;
+  }
+
+  private static EList<EAnnotation> getAnnotations(EClass eClass)
+  {
+    EList<EAnnotation> annotations = new BasicEList<EAnnotation>();
+    getAnnotations(eClass, annotations, new HashSet<EClass>());
+    return annotations;
+  }
+
+  private static void getAnnotations(EClass eClass, EList<EAnnotation> annotations, Set<EClass> visited)
+  {
+    if (visited.add(eClass))
+    {
+      for (EAnnotation annotation : eClass.getEAnnotations())
+      {
+        if (SOURCE_URI.equals(annotation.getSource()))
+        {
+          annotations.add(annotation);
+        }
+      }
+
+      for (EClass superType : eClass.getESuperTypes())
+      {
+        getAnnotations(superType, annotations, visited);
+      }
+    }
+  }
+
+  private static EStructuralFeature getPersistentFeature(String featureName, EStructuralFeature[] allPersistentFeatures)
+  {
+    for (int i = 0; i < allPersistentFeatures.length; i++)
+    {
+      EStructuralFeature feature = allPersistentFeatures[i];
+      if (feature.getName().equals(featureName))
+      {
+        return feature;
+      }
+    }
+
+    return null;
+  }
+
+  private static boolean isPersistentFeature(EStructuralFeature feature, EStructuralFeature[] allPersistentFeatures)
+  {
+    for (int i = 0; i < allPersistentFeatures.length; i++)
+    {
+      if (allPersistentFeatures[i] == feature)
+      {
+        return true;
+      }
+    }
+
+    return false;
+  }
+}
diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AbstractHorizontalClassMapping.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AbstractHorizontalClassMapping.java
index 8cce38f..4cd9795 100644
--- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AbstractHorizontalClassMapping.java
+++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AbstractHorizontalClassMapping.java
@@ -34,6 +34,7 @@
 import org.eclipse.emf.cdo.server.db.mapping.IListMapping3;
 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.DBIndexAnnotation;
 import org.eclipse.emf.cdo.server.internal.db.bundle.OM;
 import org.eclipse.emf.cdo.server.internal.db.mapping.AbstractMappingStrategy;
 import org.eclipse.emf.cdo.spi.common.commit.CDOChangeSetSegment;
@@ -255,6 +256,68 @@
           }
         }
       }
+
+      initIndices(allPersistentFeatures);
+    }
+  }
+
+  private void initIndices(EStructuralFeature[] allPersistentFeatures)
+  {
+    for (List<EStructuralFeature> features : DBIndexAnnotation.getIndices(eClass, allPersistentFeatures))
+    {
+      int size = features.size();
+      if (size > 1)
+      {
+        IDBField[] fields = new IDBField[size];
+
+        for (int i = 0; i < size; i++)
+        {
+          EStructuralFeature feature = features.get(i);
+
+          ITypeMapping typeMapping = getValueMapping(feature);
+          IDBField field = typeMapping.getField();
+          fields[i] = field;
+        }
+
+        if (!table.hasIndexFor(fields))
+        {
+          InternalDBIndex index = (InternalDBIndex)table.addIndex(IDBIndex.Type.NON_UNIQUE, fields);
+          index.setOptional(true); // Creation might fail for unsupported column type!
+        }
+      }
+      else
+      {
+        EStructuralFeature feature = features.get(0);
+        if (feature.isMany())
+        {
+          IListMapping listMapping = getListMapping(feature);
+          if (listMapping instanceof AbstractListTableMapping)
+          {
+            AbstractListTableMapping mapping = (AbstractListTableMapping)listMapping;
+
+            IDBTable table = mapping.getDBTables().iterator().next();
+            ITypeMapping typeMapping = mapping.getTypeMapping();
+            IDBField field = typeMapping.getField();
+
+            if (!table.hasIndexFor(field))
+            {
+              InternalDBIndex index = (InternalDBIndex)table.addIndex(IDBIndex.Type.NON_UNIQUE, field);
+              index.setOptional(true); // Creation might fail for unsupported column type!
+            }
+          }
+        }
+        else
+        {
+          ITypeMapping typeMapping = getValueMapping(feature);
+          IDBField field = typeMapping.getField();
+
+          if (!table.hasIndexFor(field))
+          {
+            InternalDBIndex index = (InternalDBIndex)table.addIndex(IDBIndex.Type.NON_UNIQUE, field);
+            index.setOptional(true); // Creation might fail for unsupported column type!
+          }
+        }
+      }
     }
   }