LQMGNamingStrategy.java

/*
 * Copyright (C) 2011 Everit Kft. (http://www.everit.biz)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.everit.persistence.lqmg.internal;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.bind.JAXBElement;

import org.everit.persistence.lqmg.LQMGException;
import org.everit.persistence.lqmg.internal.schema.xml.AbstractNamingRuleType;
import org.everit.persistence.lqmg.internal.schema.xml.ClassNameRuleType;
import org.everit.persistence.lqmg.internal.schema.xml.PropertyMappingType;
import org.everit.persistence.lqmg.internal.schema.xml.PropertyMappingsType;
import org.everit.persistence.lqmg.internal.schema.xml.RegexRuleType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.querydsl.codegen.EntityType;
import com.querydsl.sql.SchemaAndTable;
import com.querydsl.sql.codegen.DefaultNamingStrategy;
import com.querydsl.sql.codegen.support.ForeignKeyData;

/**
 * Customized {@link com.querydsl.sql.codegen.NamingStrategy} for LQMG.
 */
public class LQMGNamingStrategy extends DefaultNamingStrategy {

  /**
   * Constants for DB attributes.
   */
  private enum DBAttributeType {

    COLUMN("column"), FOREIGN_KEY("foreignKey"), PRIMARY_KEY("primaryKey");

    private String typeName;

    DBAttributeType(final String typeName) {
      this.typeName = typeName;
    }

    public String getTypeName() {
      return typeName;
    }
  }

  private static final String FK_PREFIX = "fk_";

  private static final Logger LOGGER = LoggerFactory.getLogger(LQMGNamingStrategy.class);

  private static final String PK_PREFIX = "pk_";

  private final Map<SchemaAndTable, String> classNameCache = new HashMap<>();

  private final ConfigurationContainer configurationContainer;

  private final Set<String> packages;

  public LQMGNamingStrategy(final ConfigurationContainer configurationContainer,
      final String[] packages) {
    this.configurationContainer = configurationContainer;
    this.packages = new HashSet<String>(Arrays.asList(packages));
  }

  @Override
  public String getClassName(final SchemaAndTable schemaAndTable) {

    String simpleName = classNameCache.get(schemaAndTable);
    if (simpleName != null) {
      return simpleName;
    }

    ConfigValue<? extends AbstractNamingRuleType> configValue =
        configurationContainer.findConfigForEntity(schemaAndTable);
    if (configValue == null) {
      return null;
    }

    AbstractNamingRuleType namingRule = configValue.namingRule;
    if (namingRule instanceof RegexRuleType) {
      RegexRuleType regexRule = (RegexRuleType) namingRule;
      String regex = regexRule.getRegex();
      String replacement = regexRule.getReplacement();
      Pattern pattern = configurationContainer.getPatternByRegex(regex);
      Matcher matcher = pattern.matcher(schemaAndTable.getTable());
      String replacedEntityName = matcher.replaceAll(replacement);
      simpleName = super.getClassName(replacedEntityName);
    } else if (namingRule instanceof ClassNameRuleType) {
      simpleName = ((ClassNameRuleType) namingRule).getClazz();
    }

    String prefix = namingRule.getPrefix();
    if (prefix != null) {
      simpleName = prefix + simpleName;
    }

    String suffix = namingRule.getSuffix();
    if (suffix != null) {
      simpleName = simpleName + suffix;
    }

    classNameCache.put(schemaAndTable, simpleName);

    if (simpleName == null) {
      throw new LQMGException(
          "Cannot resolve class name for '" + schemaAndTable.getTable() + "' table in '"
              + schemaAndTable.getSchema() + "' schema.",
          null);
    }
    return simpleName;
  }

  @Override
  public String getDefaultVariableName(final EntityType entityType) {

    SchemaAndTable schemaAndTable = getSchemaAndTable(entityType);

    String propertyName = getClassName(schemaAndTable);
    ConfigValue<? extends AbstractNamingRuleType> config =
        configurationContainer.findConfigForEntity(schemaAndTable);

    AbstractNamingRuleType namingRule = config.getNamingRule();
    String prefix = namingRule.getPrefix();
    String suffix = namingRule.getSuffix();

    if (prefix != null) {
      propertyName = propertyName.substring(prefix.length());
    }

    if (suffix != null) {
      propertyName = propertyName.substring(0, propertyName.length() - suffix.length());
    }

    propertyName = Character.toLowerCase(propertyName.charAt(0)) + propertyName.substring(1);
    return escape(entityType, propertyName);
  }

  private List<JAXBElement<PropertyMappingType>> getForeignKeyAndPrimaryKeyAndColumnForEntity(
      final EntityType entityType) {

    SchemaAndTable schemaAndTable = getSchemaAndTable(entityType);

    ConfigValue<? extends AbstractNamingRuleType> configValue =
        configurationContainer.findConfigForEntity(schemaAndTable);

    if (configValue == null) {
      return null;
    }

    AbstractNamingRuleType namingRule = configValue.getNamingRule();
    if (!(namingRule instanceof ClassNameRuleType)) {
      return null;
    }

    PropertyMappingsType propertyMappings = ((ClassNameRuleType) namingRule).getPropertyMappings();
    if (propertyMappings == null) {
      return null;
    }

    return propertyMappings.getPrimaryKeyAndForeignKeyAndColumn();
  }

  @Override
  public String getPackage(final String basePackage, final SchemaAndTable schemaAndTable) {

    String simpleName = getClassName(schemaAndTable);
    if (simpleName == null) {
      throw new LQMGException("Cannot resolve class name for '" + schemaAndTable.getTable()
          + "' table in '" + schemaAndTable.getSchema() + "' schema.", null);
    }

    ConfigValue<? extends AbstractNamingRuleType> configValue =
        configurationContainer.findConfigForEntity(schemaAndTable);
    AbstractNamingRuleType namingRule = configValue.getNamingRule();
    String packageName = namingRule.getPackage();

    return packageName;
  }

  @Override
  public String getPropertyName(final String columnName, final EntityType entityType) {
    String propertyName =
        getPropertyNameFromMapping(columnName, entityType, DBAttributeType.COLUMN);
    if (propertyName != null) {
      return propertyName;
    } else {
      return super.getPropertyName(columnName, entityType);
    }
  }

  @Override
  public String getPropertyNameForForeignKey(final String fkName, final EntityType entityType) {
    String localFkName = fkName;
    String propertyName =
        getPropertyNameFromMapping(localFkName, entityType, DBAttributeType.FOREIGN_KEY);
    if (propertyName != null) {
      return propertyName;
    } else {
      if (localFkName.toLowerCase(Locale.ENGLISH).startsWith(FK_PREFIX)) {
        localFkName = localFkName.substring(FK_PREFIX.length()) + "_" + localFkName.substring(0, 2);
      }
      return getSuperPropertyName(localFkName, entityType);
    }
  }

  @Override
  public String getPropertyNameForPrimaryKey(final String pkName, final EntityType entityType) {
    String localPkName = pkName;
    String propertyName =
        getPropertyNameFromMapping(localPkName, entityType, DBAttributeType.PRIMARY_KEY);
    if (propertyName != null) {
      return propertyName;
    } else {
      if (localPkName.toLowerCase(Locale.ENGLISH).startsWith(PK_PREFIX)) {
        localPkName = localPkName.substring(PK_PREFIX.length()) + "_" + localPkName.substring(0, 2);
      }
      return getSuperPropertyName(localPkName, entityType);
    }
  }

  private String getPropertyNameFromMapping(final String originalName,
      final EntityType entityType,
      final DBAttributeType dbAttributeType) {

    List<JAXBElement<PropertyMappingType>> foreignKeyAndPrimaryKeyAndColumn =
        getForeignKeyAndPrimaryKeyAndColumnForEntity(entityType);

    if (foreignKeyAndPrimaryKeyAndColumn == null) {
      return null;
    }

    PropertyMappingType selectedMapping = null;
    Iterator<JAXBElement<PropertyMappingType>> iterator =
        foreignKeyAndPrimaryKeyAndColumn.iterator();
    while (iterator.hasNext() && (selectedMapping == null)) {
      JAXBElement<?> jaxbElement = iterator.next();
      if (dbAttributeType.getTypeName().equals(jaxbElement.getName().getLocalPart())) {
        PropertyMappingType propertyMapping = (PropertyMappingType) jaxbElement.getValue();
        if (propertyMapping.getName().equals(originalName)) {
          selectedMapping = propertyMapping;
        }
      }
    }

    if (selectedMapping != null) {
      return selectedMapping.getProperty();
    } else {
      return null;
    }
  }

  private SchemaAndTable getSchemaAndTable(final EntityType entityType) {
    String tableName = entityType.getData().get("table").toString();
    String schemaName = (String) entityType.getData().get("schema");

    SchemaAndTable schemaAndTable = new SchemaAndTable(schemaName, tableName);
    return schemaAndTable;
  }

  private String getSuperPropertyName(final String columnName, final EntityType entityType) {
    return super.getPropertyName(columnName, entityType);
  }

  private boolean shouldGenerate(final SchemaAndTable schemaAndTable) {

    ConfigValue<? extends AbstractNamingRuleType> configValue =
        configurationContainer.findConfigForEntity(schemaAndTable);
    if (configValue == null) {
      LOGGER.info("No configuration for table '" + schemaAndTable.getTable() + "' in schema '"
          + schemaAndTable.getSchema());
      return false;
    }

    AbstractNamingRuleType namingRule = configValue.getNamingRule();

    String javaPackage = namingRule.getPackage();
    if ((packages.size() > 0) && !packages.contains(javaPackage)) {
      LOGGER.info("Java package '" + javaPackage + "' is not included, ignoring table '"
          + schemaAndTable.getTable() + "' from schema '." + schemaAndTable.getSchema());
      return false;
    }

    return true;
  }

  @Override
  public boolean shouldGenerateClass(final SchemaAndTable schemaAndTable) {

    if (shouldGenerate(schemaAndTable)) {
      return super.shouldGenerateClass(schemaAndTable);
    }

    return false;
  }

  @Override
  public boolean shouldGenerateForeignKey(final SchemaAndTable schemaAndTable,
      final ForeignKeyData foreignKeyData) {

    String fkSchemaName = foreignKeyData.getSchema();
    String fkTableName = foreignKeyData.getTable();
    SchemaAndTable fkSchemaAndTable = new SchemaAndTable(fkSchemaName, fkTableName);

    if (shouldGenerate(fkSchemaAndTable)) {
      return super.shouldGenerateForeignKey(schemaAndTable, foreignKeyData);
    }

    return false;
  }

}