/**
   * Tests that single- and multi-column user type mappings work correctly. Also Checks that the
   * "sqlType" property is honoured.
   */
  public void testUserTypeMappings() {
    DefaultGrailsDomainConfiguration config = getDomainConfig(MULTI_COLUMN_USER_TYPE_DEFINITION);
    PersistentClass persistentClass = config.getClassMapping("Item");

    // First check the "name" property and its associated column.
    Property nameProperty = persistentClass.getProperty("name");
    assertEquals(1, nameProperty.getColumnSpan());
    assertEquals("name", nameProperty.getName());

    Column column = (Column) nameProperty.getColumnIterator().next();
    assertEquals("s_name", column.getName());
    assertEquals("text", column.getSqlType());

    // Next the "other" property.
    Property otherProperty = persistentClass.getProperty("other");
    assertEquals(1, otherProperty.getColumnSpan());
    assertEquals("other", otherProperty.getName());

    column = (Column) otherProperty.getColumnIterator().next();
    assertEquals("other", column.getName());
    assertEquals("wrapper-characters", column.getSqlType());
    assertEquals(MyUserType.class.getName(), column.getValue().getType().getName());
    assertTrue(column.getValue() instanceof SimpleValue);
    SimpleValue v = (SimpleValue) column.getValue();
    assertEquals("myParam1", v.getTypeParameters().get("param1"));
    assertEquals("myParam2", v.getTypeParameters().get("param2"));

    // And now for the "price" property, which should have two
    // columns.
    Property priceProperty = persistentClass.getProperty("price");
    assertEquals(2, priceProperty.getColumnSpan());
    assertEquals("price", priceProperty.getName());

    Iterator<?> colIter = priceProperty.getColumnIterator();
    column = (Column) colIter.next();
    assertEquals("value", column.getName());
    assertNull("SQL type should have been 'null' for 'value' column.", column.getSqlType());

    column = (Column) colIter.next();
    assertEquals("currency_code", column.getName());
    assertEquals("text", column.getSqlType());
  }
  public String generateAnnColumnAnnotation(Property property) {
    StringBuffer annotations = new StringBuffer("    ");
    boolean insertable = property.isInsertable();
    boolean updatable = property.isUpdateable();
    if (property.isComposite()) {
      annotations.append("@" + importType("javax.persistence.AttributeOverrides") + "( {");
      Component component = (Component) property.getValue();
      Iterator<?> subElements = component.getPropertyIterator();
      buildRecursiveAttributeOverride(subElements, null, property, annotations);
      annotations.setLength(annotations.length() - 2);
      annotations.append(" } )");
    } else {
      if (property.getColumnSpan() == 1) {
        Selectable selectable = (Selectable) property.getColumnIterator().next();
        buildColumnAnnotation(selectable, annotations, insertable, updatable);
      } else {
        Iterator<?> columns = property.getColumnIterator();
        annotations
            .append("@")
            .append(importType("org.hibernate.annotations.Columns"))
            .append("( { ");
        while (columns.hasNext()) {
          Selectable selectable = (Selectable) columns.next();

          if (selectable.isFormula()) {
            // TODO formula in multicolumns not supported by annotations
            // annotations.append("/* TODO formula in multicolumns not supported by annotations
            // */");
          } else {
            annotations.append("\n        ");
            buildColumnAnnotation(selectable, annotations, insertable, updatable);
            annotations.append(", ");
          }
        }
        annotations.setLength(annotations.length() - 2);
        annotations.append(" } )");
      }
    }
    return annotations.toString();
  }
    public GenerationStrategyPair buildPair() {
      if (hadInMemoryGeneration && hadInDatabaseGeneration) {
        throw new ValueGenerationStrategyException(
            "Composite attribute ["
                + mappingProperty.getName()
                + "] contained both in-memory"
                + " and in-database value generation");
      } else if (hadInMemoryGeneration) {
        throw new NotYetImplementedException(
            "Still need to wire in composite in-memory value generation");

      } else if (hadInDatabaseGeneration) {
        final Component composite = (Component) mappingProperty.getValue();

        // we need the numbers to match up so we can properly handle 'referenced sql column values'
        if (inDatabaseStrategies.size() != composite.getPropertySpan()) {
          throw new ValueGenerationStrategyException(
              "Internal error : mismatch between number of collected in-db generation strategies"
                  + " and number of attributes for composite attribute : "
                  + mappingProperty.getName());
        }

        // the base-line values for the aggregated InDatabaseValueGenerationStrategy we will build
        // here.
        GenerationTiming timing = GenerationTiming.INSERT;
        boolean referenceColumns = false;
        String[] columnValues = new String[composite.getColumnSpan()];

        // start building the aggregate values
        int propertyIndex = -1;
        int columnIndex = 0;
        Iterator subProperties = composite.getPropertyIterator();
        while (subProperties.hasNext()) {
          propertyIndex++;
          final Property subProperty = (Property) subProperties.next();
          final InDatabaseValueGenerationStrategy subStrategy =
              inDatabaseStrategies.get(propertyIndex);

          if (subStrategy.getGenerationTiming() == GenerationTiming.ALWAYS) {
            // override the base-line to the more often "ALWAYS"...
            timing = GenerationTiming.ALWAYS;
          }
          if (subStrategy.referenceColumnsInSql()) {
            // override base-line value
            referenceColumns = true;
          }
          if (subStrategy.getReferencedColumnValues() != null) {
            if (subStrategy.getReferencedColumnValues().length != subProperty.getColumnSpan()) {
              throw new ValueGenerationStrategyException(
                  "Internal error : mismatch between number of collected 'referenced column values'"
                      + " and number of columns for composite attribute : "
                      + mappingProperty.getName()
                      + '.'
                      + subProperty.getName());
            }
            System.arraycopy(
                subStrategy.getReferencedColumnValues(),
                0,
                columnValues,
                columnIndex,
                subProperty.getColumnSpan());
          }
        }

        // then use the aggregated values to build the InDatabaseValueGenerationStrategy
        return new GenerationStrategyPair(
            new InDatabaseValueGenerationStrategyImpl(timing, referenceColumns, columnValues));
      } else {
        return NO_GEN_PAIR;
      }
    }
  @SuppressWarnings({CompilerWarnings.UNCHECKED})
  private void buildEntityMetadatas() throws Exception {
    SessionFactoryImpl sessionFactory =
        ((SessionFactoryImpl) this.entityManagerFactory.getSessionFactory());
    Class<?> entityBindingMappedClass;
    Class<? extends SdcctEntity> entityMappedClass;
    String entityName, entityPropName, entityPropFieldName;
    EntityMetadata entityMetadata;
    Map<String, PropertyMetadata> entityPropMetadatas;
    IndexedTypeDescriptor indexedEntityDesc;
    Method entityPropGetterMethod;
    boolean entityIndexed, entityPropIndexed;
    PropertyDescriptor indexedEntityPropDesc;
    PropertyMetadata entityPropMetadata;
    BidiMap<Integer, String> entityPropOrder;
    String[] entityPropOrderNames;

    for (PersistentClass entityBinding : metadata.getEntityBindings()) {
      if (((entityBindingMappedClass = entityBinding.getMappedClass()) == null)
          || !SdcctEntity.class.isAssignableFrom(entityBindingMappedClass)) {
        continue;
      }

      entityPropMetadatas =
          (entityMetadata =
                  new EntityMetadataImpl(
                      (entityName = entityBinding.getEntityName()),
                      (entityIndexed =
                          (indexedEntityDesc =
                                  searchIntegrator.getIndexedTypeDescriptor(
                                      (entityMappedClass =
                                          ((Class<? extends SdcctEntity>)
                                              entityBindingMappedClass))))
                              .isIndexed()),
                      entityMappedClass,
                      entityBinding.getTable().getName()))
              .getProperties();

      this.entityMetadatas.put(
          ((Class<? extends SdcctEntity>) entityMappedClass.getInterfaces()[0]), entityMetadata);

      for (Property entityProp :
          IteratorUtils.asIterable(
              IteratorUtils.chainedIterator(
                  ((Iterator<Property>) entityBinding.getRootClass().getPropertyIterator()),
                  ((Iterator<Property>) entityBinding.getPropertyIterator())))) {
        entityPropName = entityProp.getName();
        entityPropGetterMethod = entityProp.getGetter(entityMappedClass).getMethod();

        if (entityProp.getColumnSpan() == 0) {
          continue;
        }

        entityPropIndexed =
            (entityIndexed
                && entityPropGetterMethod.isAnnotationPresent(Fields.class)
                && ((indexedEntityPropDesc = indexedEntityDesc.getProperty(entityPropName)) != null)
                && !indexedEntityPropDesc.isId());

        entityPropMetadatas.put(
            entityPropName,
            (entityPropMetadata =
                new PropertyMetadataImpl(
                    entityPropName,
                    entityPropIndexed,
                    ((Column) entityProp.getColumnIterator().next()).getName(),
                    entityProp.getType())));

        if (entityPropIndexed) {
          for (Field entityPropFieldAnno :
              entityPropGetterMethod.getAnnotation(Fields.class).value()) {
            if (entityPropFieldAnno.analyze() == Analyze.NO) {
              continue;
            }

            entityPropFieldName = entityPropFieldAnno.name();

            switch (entityPropFieldAnno.analyzer().definition()) {
              case DbAnalyzerNames.EDGE_NGRAM:
                entityPropMetadata.setEdgeNgramFieldName(entityPropFieldName);
                break;

              case DbAnalyzerNames.LOWERCASE:
                entityPropMetadata.setLowercaseFieldName(entityPropFieldName);
                break;

              case DbAnalyzerNames.NGRAM:
                entityPropMetadata.setNgramFieldName(entityPropFieldName);
                break;

              case DbAnalyzerNames.PHONETIC:
                entityPropMetadata.setPhoneticFieldName(entityPropFieldName);
                break;
            }
          }
        }
      }

      entityMetadata.setIdProperty(
          entityPropMetadatas.get(entityBinding.getIdentifierProperty().getName()));

      entityPropOrder = entityMetadata.getPropertyOrder();
      entityPropOrderNames = sessionFactory.getEntityPersister(entityName).getPropertyNames();

      for (int a = 0; a < entityPropOrderNames.length; a++) {
        entityPropOrder.put(a, entityPropOrderNames[a]);
      }
    }

    LOGGER.debug(
        String.format(
            "Processed metadata for %d entities: [%s]",
            this.entityMetadatas.size(),
            this.entityMetadatas
                .values()
                .stream()
                .map(NamedBean::getName)
                .collect(Collectors.joining(", "))));
  }