All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.tentackle.model.impl.ModelImpl Maven / Gradle / Ivy

The newest version!
/*
 * Tentackle - http://www.tentackle.org.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.tentackle.model.impl;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import org.tentackle.common.BasicStringHelper;
import org.tentackle.common.Constants;
import org.tentackle.common.ExceptionHelper;
import org.tentackle.common.Service;
import org.tentackle.model.Attribute;
import org.tentackle.model.Entity;
import org.tentackle.model.EntityFactory;
import org.tentackle.model.ForeignKey;
import org.tentackle.model.InheritanceType;
import org.tentackle.model.Integrity;
import org.tentackle.model.Model;
import org.tentackle.model.ModelDefaults;
import org.tentackle.model.ModelException;
import org.tentackle.model.Relation;
import org.tentackle.model.RelationType;
import org.tentackle.model.SelectionType;
import org.tentackle.model.TrackType;
import org.tentackle.sql.Backend;

/**
 * Model implementation.
 *
 * @author harald
 */
@Service(Model.class)
public class ModelImpl implements Model {

  /** cached entities mapped by filename. */
  private final Map mapByFileName;

  /** cached entities mapped by entity name. */
  private final Map mapByEntityName;

  /** cached entities mapped by entity id. */
  private final Map mapByClassId;

  /** model dirs processed so far. */
  private final Map modelDirs;   // 

  /** the entity factory. */
  private final EntityFactory entityFactory;

  /** the foreign keys. */
  private Collection foreignKeys;

  /** map schema names. */
  private boolean mapSchemas;


  /**
   * Creates a model.
   */
  public ModelImpl() {
    entityFactory = createEntityFactory();
    mapByEntityName = new TreeMap<>();    // tree map for implicit sort by name
    mapByClassId = new HashMap<>();
    mapByFileName = new HashMap<>();
    modelDirs = new HashMap<>();
  }

  @Override
  public void setSchemaNameMapped(boolean mapSchemas) {
    this.mapSchemas = mapSchemas;
  }

  @Override
  public boolean isSchemaNameMapped() {
    return mapSchemas;
  }


  @Override
  public EntityFactory getEntityFactory() {
    return entityFactory;
  }


  @Override
  public synchronized void loadModel(String modelDir, ModelDefaults defaults) throws ModelException {

    ModelDirectory dir = modelDirs.get(modelDir);
    if (dir == null || dir.hasChanged()) {
      dir = new ModelDirectory(modelDir, defaults);
      modelDirs.put(modelDir, dir);
      File[] files = dir.getFile().listFiles();
      if (files != null) {
        for (File file : files) {
          if (!file.isHidden()) {
            loadByFileName(defaults, file.getPath(), false);
          }
        }
      }
      updateRelations(defaults);
    }

    if (defaults != null) {
      boolean updateRelations = false;
      if (defaults.getTrackType() != null) {
        for (ModelEntity modelEntity: mapByEntityName.values()) {
          Entity entity = modelEntity.getEntity();
          if (!entity.getOptions().noModelDefaults() &&
              defaults.getTrackType().ordinal() > entity.getOptions().getTrackType().ordinal()) {
            ((EntityImpl) entity).getOptions().setTrackType(defaults.getTrackType());
            updateRelations = true;
          }
        }
      }
      if (defaults.getDeletionCascaded() != null && defaults.getDeletionCascaded()) {
        for (ModelEntity modelEntity: mapByEntityName.values()) {
          Entity entity = modelEntity.getEntity();
          if (!entity.getOptions().noModelDefaults()) {
            for (Relation relation: entity.getRelations()) {
              if (relation.isComposite() && !relation.isDeletionCascaded()) {
                ((RelationImpl) relation).setDeletionCascaded(defaults.getDeletionCascaded());
                updateRelations = true;
              }
            }
          }
        }
      }
      if (updateRelations) {
        updateRelations(defaults);
      }
    }
  }


  @Override
  public synchronized void clearModel() {
    mapByEntityName.clear();
    mapByClassId.clear();
    mapByFileName.clear();
    modelDirs.clear();
  }

  @Override
  public void refreshModel() throws ModelException {
    for (ModelDirectory modelDir: modelDirs.values()) {
      modelDir.markDirty();
      loadModel(modelDir.getFile().getAbsolutePath(), modelDir.getModelDefaults());
    }
  }

  @Override
  public synchronized Collection getAllEntitites() throws ModelException {
    List list = new ArrayList<>();

    boolean refreshed = false;
    do {
      for (ModelEntity modelEntity: mapByEntityName.values()) {
        if (modelEntity.hasChanged()) {
          refreshModel();
          list.clear();
          refreshed = true;
          break;
        }
        list.add(modelEntity.getEntity());
      }
    } while (refreshed);

    return list;
  }


  @Override
  public synchronized Entity getByFileName(String fileName) throws ModelException {
    ModelEntity modelEntity = mapByFileName.get(fileName);
    if (modelEntity != null && modelEntity.hasChanged()) {
      refreshModel();
      modelEntity = mapByFileName.get(fileName);
    }
    return modelEntity == null ? null : modelEntity.getEntity();
  }


  @Override
  public synchronized Entity getByEntityName(String entityName) throws ModelException {
    String name = entityName.toLowerCase();
    ModelEntity modelEntity = mapByEntityName.get(name);
    if (modelEntity != null && modelEntity.hasChanged()) {
      refreshModel();
      modelEntity = mapByEntityName.get(name);
    }
    return modelEntity == null ? null : modelEntity.getEntity();
  }

  @Override
  public synchronized Entity getByClassId(int classId) throws ModelException {
    ModelEntity modelEntity = mapByClassId.get(classId);
    if (modelEntity != null && modelEntity.hasChanged()) {
      refreshModel();
      modelEntity = mapByClassId.get(classId);
    }
    return modelEntity == null ? null : modelEntity.getEntity();
  }


  @Override
  public Entity loadByFileName(ModelDefaults defaults, String fileName) throws ModelException {
    return loadByFileName(defaults, fileName, true);
  }


  /**
   * Loads an entity for a given mapfile name.
* Entities are cached so they are loaded and parsed only once. * * @param defaults the model defaults * @param fileName the name of the mapfile * @param updateRelations true if update the related entites in the model * @return the entity * @throws ModelException if no such mapfile or parsing error */ protected synchronized Entity loadByFileName(ModelDefaults defaults, String fileName, boolean updateRelations) throws ModelException { ModelEntity modelEntity = mapByFileName.get(fileName); if (modelEntity != null) { // check modification time if (modelEntity.hasChanged()) { modelEntity = null; // force reload } } if (modelEntity == null) { // load from file StringBuilder modelSource = new StringBuilder(); try (BufferedReader reader = new BufferedReader(createFileReader(fileName))) { char[] buf = new char[1024]; int len; while ((len = reader.read(buf)) != -1) { modelSource.append(buf, 0, len); } } catch (IOException ex) { throw new ModelException("reading model '" + fileName + "' failed", ex); } // parse the model source try { Entity entity = getEntityFactory().createEntity(modelSource.toString(), defaults); modelEntity = new ModelEntity(entity, fileName); } catch (ModelException mex) { String msg = "parsing '" + fileName + "' failed:\n " + ExceptionHelper.concatenateMessages(mex); throw new ModelException(msg, mex); } if (mapSchemas) { String tableName = modelEntity.getEntity().getTableName(); if (tableName != null) { ((EntityImpl) modelEntity.getEntity()).setTableName(tableName.replace('.', '_')); } } // append to maps mapByFileName.put(fileName, modelEntity); // must be new... ModelEntity oldModelEntity = mapByEntityName.put(modelEntity.getEntity().getName().toLowerCase(), modelEntity); if (modelEntity.getEntity().getClassId() != 0) { if (oldModelEntity != null && oldModelEntity.getEntity().getClassId() != modelEntity.getEntity().getClassId()) { throw new ModelException("duplicate entity " + modelEntity.getEntity().getName() + " in file " + fileName, modelEntity.getEntity()); } oldModelEntity = mapByClassId.put(modelEntity.getEntity().getClassId(), modelEntity); if (oldModelEntity != null && !oldModelEntity.getEntity().getName().equals(modelEntity.getEntity().getName())) { throw new ModelException("duplicate entity id " + modelEntity.getEntity().getClassId() + " for " + modelEntity.getEntity().getName() + ", already assigned to " + oldModelEntity.getEntity().getName(), modelEntity.getEntity()); } } foreignKeys = null; if (updateRelations) { updateRelations(defaults, modelEntity); } } return modelEntity.getEntity(); } /** * Updates the inheritance links and relations of all entities in the model. * * @param defaults the model defaults * @throws ModelException if relations or inheritance misconfigured */ protected void updateRelations(ModelDefaults defaults) throws ModelException { updateRelations(defaults, null); } /** * Updates all relations. * * @param defaults the model defaults * @param loadedModelEntity the entity that caused the update, null if none * @throws ModelException if relations or inheritance misconfigured */ protected void updateRelations(ModelDefaults defaults, ModelEntity loadedModelEntity) throws ModelException { StringBuilder exMsg = new StringBuilder(); Set exEntities = new HashSet<>(); Entity loadedEntity = loadedModelEntity == null ? null : loadedModelEntity.getEntity(); // set inheritance entities links for (ModelEntity modelEntity: mapByEntityName.values()) { modelEntity.getEntity().getSubEntities().clear(); } for (ModelEntity modelEntity: mapByEntityName.values()) { Entity entity = modelEntity.getEntity(); if (entity.getSuperEntityName() != null) { Entity superEntity = getByEntityName(entity.getSuperEntityName()); if (superEntity == null) { if (loadedEntity == null || loadedEntity.getAssociatedEntities().contains(entity)) { if (exMsg.length() > 0) { exMsg.append('\n'); } exMsg.append("no such super entity '"); exMsg.append(entity.getSuperEntityName()); exMsg.append("' to be extended by '"); exMsg.append(entity); exMsg.append("'"); exEntities.add(entity); } } else { ((EntityImpl) entity).setSuperEntity(superEntity); if (superEntity.getInheritanceType() == InheritanceType.NONE) { if (loadedEntity == null || loadedEntity.getAssociatedEntities().contains(entity)) { if (exMsg.length() > 0) { exMsg.append('\n'); } exMsg.append("super entity '"); exMsg.append(superEntity); exMsg.append("' extended by '"); exMsg.append(entity); exMsg.append("' is not extensible (missing inheritance := ... ?)"); exEntities.add(entity); exEntities.add(superEntity); } } superEntity.getSubEntities().add(entity); } } } // update relations for (ModelEntity modelEntity: mapByEntityName.values()) { Entity entity = modelEntity.getEntity(); entity.getReferencingRelations().clear(); entity.getCompositePaths().clear(); entity.getDeepReferences().clear(); entity.getDeepReferencesToComponents().clear(); ((EntityImpl) entity).setDeeplyReferenced(false); for (Relation relation: entity.getRelations()) { ((RelationImpl) relation).setForeignRelation(null); Entity foreignEntity = getByEntityName(relation.getClassName()); if (foreignEntity == null) { if (loadedEntity == null || loadedEntity.getAssociatedEntities().contains(entity)) { // log error only if class is associated to the updated one if (exMsg.length() > 0) { exMsg.append('\n'); } exMsg.append("no such entity: "); exMsg.append(relation.getClassName()); exEntities.add(entity); } } else { ((RelationImpl) relation).setForeignEntity(foreignEntity); if (relation.getRelationType() == RelationType.LIST) { String attributeName; if (relation.getMethodName() != null) { attributeName = relation.getMethodName(); } else { attributeName = relation.getEntity().getName() + "Id"; } attributeName = BasicStringHelper.firstToLower(attributeName); Attribute foreignAttribute = foreignEntity.getAttributeByJavaName(attributeName, true); if (foreignAttribute == null) { if (loadedEntity == null || loadedEntity.getAssociatedEntities().contains(entity)) { if (exMsg.length() > 0) { exMsg.append('\n'); } exMsg.append("no such attribute: "); exMsg.append(attributeName); exMsg.append(" in "); exMsg.append(foreignEntity); exEntities.add(entity); exEntities.add(foreignEntity); } } else { ((RelationImpl) relation).setForeignAttribute(foreignAttribute); } if (relation.getNmName() != null) { Relation nmRelation = foreignEntity.getRelation(relation.getNmName(), false); if (nmRelation == null) { if (loadedEntity == null || loadedEntity.getAssociatedEntities().contains(entity)) { if (exMsg.length() > 0) { exMsg.append('\n'); } exMsg.append("no such nm-relation: "); exMsg.append(relation.getNmName()); exMsg.append(" in "); exMsg.append(foreignEntity); exEntities.add(entity); exEntities.add(foreignEntity); } } else { if (nmRelation.isComposite() || nmRelation.getRelationType() == RelationType.LIST) { if (loadedEntity == null || loadedEntity.getAssociatedEntities().contains(entity)) { if (exMsg.length() > 0) { exMsg.append('\n'); } exMsg.append("nm-relation "); exMsg.append(nmRelation.getName()); exMsg.append(" in "); exMsg.append(foreignEntity); exMsg.append(" must be non-composite object"); exEntities.add(entity); exEntities.add(foreignEntity); } } if (nmRelation.getClassName().equals(entity.getName())) { if (loadedEntity == null || loadedEntity.getAssociatedEntities().contains(entity)) { if (exMsg.length() > 0) { exMsg.append('\n'); } exMsg.append("nm-relation "); exMsg.append(nmRelation.getName()); exMsg.append(" in "); exMsg.append(foreignEntity); exMsg.append(" points back to "); exMsg.append(entity); exEntities.add(entity); exEntities.add(foreignEntity); } } if (nmRelation.getSelectionType() == SelectionType.LAZY && !nmRelation.isSerialized()) { // PdoRelations-Wurblet will generate --join which makes only sense if pdos are transferred via network ((RelationImpl) nmRelation).setSerialized(true); // retrieve from server ((RelationImpl) nmRelation).setClearOnRemoteSave(true); // but don't send back to server } ((RelationImpl) relation).setNmRelation(nmRelation); ((RelationImpl) nmRelation).setDefiningNmRelation(relation); } } } } } } // update referencing relations for (ModelEntity modelEntity: mapByEntityName.values()) { Entity entity = modelEntity.getEntity(); for (ModelEntity otherModelEntity: mapByEntityName.values()) { Entity otherEntity = otherModelEntity.getEntity(); for (Relation relation: otherEntity.getRelations()) { Entity foreignEntity = relation.getForeignEntity(); if (foreignEntity != null) { if (foreignEntity.equals(entity)) { entity.getReferencingRelations().add(relation); } } } } } // update foreign relations for (ModelEntity modelEntity: mapByEntityName.values()) { Entity entity = modelEntity.getEntity(); for (Relation relation: entity.getReferencingRelations()) { if (relation.getForeignRelation() == null) { // find a relation with opposite attributes for (Relation otherRelation: relation.getForeignEntity().getTableRelations()) { if (Objects.equals(relation.getEntity(), otherRelation.getForeignEntity()) && Objects.equals(relation.getAttribute(), otherRelation.getForeignAttribute())) { ((RelationImpl) relation).setForeignRelation(otherRelation); ((RelationImpl) otherRelation).setForeignRelation(relation); break; } } } } } // update composite paths and inspect model for root, rootid and rootclassid for (ModelEntity modelEntity: mapByEntityName.values()) { Entity entity = modelEntity.getEntity(); boolean isRootEntity = true; for (Relation relation: entity.getAllReferencingRelations()) { if (relation.isComposite()) { isRootEntity = false; break; } } ((EntityImpl) entity).setRootEntityAccordingToModel(isRootEntity); if (isRootEntity) { for (Relation relation: entity.getAllRelations()) { if (relation.isComposite()) { List compositePath = new ArrayList<>(); compositePath.add(relation); updateCompositePath(compositePath, relation.getForeignEntity()); } } } } // determine deep references and set rootid,rootclassid options for (ModelEntity modelEntity: mapByEntityName.values()) { Entity entity = modelEntity.getEntity(); /** * The entity should provide a root class id if it is a composite of more than one root entities * and holds exactly one root attribute column (2 means N:M relation, for example). */ Set rootEntities = entity.getRootEntities(); if (rootEntities.size() > 1 && entity.getRootAttribute() != null) { ((EntityImpl) entity).setProvidingRootClassIdAccordingToModel(true); } // or there is a non-composite reference from an entity which is not one of its root entities (deep reference) // and the entity does not provide a fixed rootClassId. // deep references would otherwise be rejected by the security manager because the // rootId/rootClassId would not match with the domain context's root entity. if (!rootEntities.isEmpty()) { // is a component of at least one root entity for (Relation relation: entity.getReferencingRelationsIncludingSubEntities()) { if (!relation.isComposite() && (relation.getForeignRelation() == null || !relation.getForeignRelation().isComposite()) && !rootEntities.containsAll(relation.getEntity().getRootEntities())) { // this is a deep reference ((RelationImpl) relation).setDeepReference(true); ((EntityImpl) entity).setDeeplyReferenced(true); entity.getDeepReferences().add(relation); for (List compositePath: entity.getCompositePaths()) { for (Relation compositeRelation: compositePath) { ((EntityImpl) compositeRelation.getEntity()).setDeeplyReferenced(true); } } for (Entity rootEntity: rootEntities) { rootEntity.getDeepReferencesToComponents().add(relation); } if (entity.getRootEntity() == null) { // need rootclassid column ((EntityImpl) entity).setProvidingRootClassIdAccordingToModel(true); } } } } // The entity should provide a rootId if it is a composite with a relation path of more than one hops // or an OBJECT-relation for (List compositePath: entity.getCompositePaths()) { if (compositePath.size() > 1 || compositePath.get(compositePath.size() - 1).getRelationType() == RelationType.OBJECT) { ((EntityImpl) entity).setProvidingRootIdAccordingToModel(true); break; } } } // applies model defaults if (defaults != null) { for (ModelEntity modelEntity : mapByEntityName.values()) { Entity entity = modelEntity.getEntity(); if (!entity.getOptions().noModelDefaults()) { if (defaults.getTrackType() != null && defaults.getTrackType().ordinal() > entity.getOptions().getTrackType().ordinal()) { ((EntityOptionsImpl) entity.getOptions()).setTrackType(defaults.getTrackType()); } if (Boolean.TRUE.equals(defaults.getRoot()) && entity.isRootEntityAccordingToModel()) { ((EntityImpl) entity.getTopSuperEntity()).getOptions().setRootEntity(true); } if (Boolean.TRUE.equals(defaults.getRootClassId()) && entity.isProvidingRootClassIdAccordingToModel()) { ((EntityImpl) entity.getTopSuperEntity()).getOptions().setRootClassIdProvided(true); } if (Boolean.TRUE.equals(defaults.getRootId()) && entity.isProvidingRootIdAccordingToModel()) { ((EntityImpl) entity.getTopSuperEntity()).getOptions().setRootIdProvided(true); } if (defaults.getRemote() != null) { ((EntityOptionsImpl) entity.getOptions()).setRemote(defaults.getRemote()); } } } } // add/remove rootId, rootClassId to/from model for (ModelEntity modelEntity: mapByEntityName.values()) { Entity entity = modelEntity.getEntity(); if (entity.getOptions().isRootClassIdProvided()) { if (entity.getAttributeByColumnName(Constants.CN_ROOTCLASSID, true) == null) { ((EntityImpl) entity).getOptions().applyOption(EntityOptionsImpl.OPTION_ROOTCLASSID, Boolean.TRUE); } } else { Attribute rootClassAttribute = entity.getAttributeByColumnName(Constants.CN_ROOTCLASSID, false); if (rootClassAttribute != null) { entity.getAttributes().remove(rootClassAttribute); } } if (entity.getOptions().isRootIdProvided()) { if (entity.getAttributeByColumnName(Constants.CN_ROOTID, true) == null) { ((EntityImpl) entity).getOptions().applyOption(EntityOptionsImpl.OPTION_ROOTID, Boolean.TRUE); } } else { Attribute rootAttribute = entity.getAttributeByColumnName(Constants.CN_ROOTID, false); if (rootAttribute != null) { entity.getAttributes().remove(rootAttribute); } } } // do some automatic settings for (ModelEntity modelEntity: mapByEntityName.values()) { Entity entity = modelEntity.getEntity(); for (Relation relation: entity.getRelations()) { if (relation.getEntity().getOptions().getTrackType().isTracked()) { ((RelationImpl) relation).setTracked(true); } if (relation.isComposite()) { if (relation.getRelationType() == RelationType.LIST && relation.getLinkMethodName() == null) { // check if there is a lazy object relation back to me Relation parentRelation = relation.getForeignEntity().getRelation(entity.getName(), true); if (parentRelation != null && parentRelation.getRelationType() == RelationType.OBJECT && parentRelation.getSelectionType() == SelectionType.LAZY) { // 1:N composite lazy relations without a linkMethod don't make sense ((RelationImpl) relation).setLinkMethodName("set" + entity.getName()); } } if (relation.getLinkMethodName() != null) { // if a link is defined for a composite (even for OBJECT), it will be referenced ((RelationImpl) relation).setReferenced(true); } if (!relation.isTracked() && entity.getIntegrity().isCompositesCheckedByDatabase()) { if (exMsg.length() > 0) { exMsg.append('\n'); } exMsg.append("untracked relation ").append(relation).append(" not allowed in conjunction with "). append(entity.getIntegrity()).append("-integrity in "); exMsg.append(entity); exEntities.add(entity); exEntities.add(relation.getForeignEntity()); } } } } // validate configuration depending on other entities for (ModelEntity modelEntity: mapByEntityName.values()) { Entity entity = modelEntity.getEntity(); try { ((EntityImpl) entity).validateRelated(); } catch (ModelException mex) { if (loadedEntity == null || loadedEntity.getAssociatedEntities().contains(entity)) { if (exMsg.length() > 0) { exMsg.append('\n'); } exMsg.append(mex.getMessage()); exMsg.append(" in "); exMsg.append(entity); exEntities.add(entity); exEntities.addAll(mex.getEntities()); } } } // verify that columns are defined only once in the inheritance hierarchy for (ModelEntity modelEntity: mapByEntityName.values()) { Entity entity = modelEntity.getEntity(); if (entity.getSuperEntity() == null && !entity.getInheritanceType().isMappingToNoTable()) { // check Map columnMap = new HashMap<>(); // map by column name Map nameMap = new HashMap<>(); // map by java name List allAttributes = new ArrayList<>(); allAttributes.addAll(entity.getAttributes()); for (Entity child: entity.getAllSubEntities()) { allAttributes.addAll(child.getAttributes()); } for (Attribute attribute: allAttributes) { Attribute oldAttribute = nameMap.put(attribute.getJavaName().toLowerCase(), attribute); if (oldAttribute != null && (loadedEntity == null || loadedEntity.getAssociatedEntities().contains(entity))) { if (exMsg.length() > 0) { exMsg.append('\n'); } exMsg.append("attribute '").append(attribute.getJavaName()).append("' in "). append(attribute.getEntity()); if (attribute.getSourceLine() != null) { exMsg.append("(").append(attribute.getSourceLine()).append(")"); } exMsg.append(" already defined in ").append(oldAttribute.getEntity()); if (oldAttribute.getSourceLine() != null) { exMsg.append("(").append(oldAttribute.getSourceLine()).append(")"); } exEntities.add(entity); exEntities.add(attribute.getEntity()); exEntities.add(oldAttribute.getEntity()); } Attribute oldColumn = columnMap.put(attribute.getColumnName().toLowerCase(), attribute); if (oldAttribute == null && // log same attrib only once oldColumn != null && (loadedEntity == null || loadedEntity.getAssociatedEntities().contains(entity))) { if (exMsg.length() > 0) { exMsg.append('\n'); } exMsg.append("column '").append(attribute.getColumnName()).append("' in "). append(attribute.getEntity()); if (attribute.getSourceLine() != null) { exMsg.append("(").append(attribute.getSourceLine()).append(")"); } exMsg.append(" already defined in ").append(oldColumn.getEntity()); if (oldColumn.getSourceLine() != null) { exMsg.append("(").append(oldColumn.getSourceLine()).append(")"); } exMsg.append(" as attribute '").append(attribute).append("'"); exEntities.add(entity); exEntities.add(attribute.getEntity()); exEntities.add(oldColumn.getEntity()); } } } } /** * generate the table aliases. */ Map aliasMap = new HashMap<>(); for (ModelEntity modelEntity: mapByEntityName.values()) { Entity entity = modelEntity.getEntity(); if (entity.getTableAlias() != null) { Entity dupAliasEntity = aliasMap.get(entity.getTableAlias()); if (dupAliasEntity != null) { if (exMsg.length() > 0) { exMsg.append('\n'); } exMsg.append("table alias '").append(entity.getTableAlias()).append("' defined more than once in "); exMsg.append(entity).append(" and ").append(dupAliasEntity); exEntities.add(entity); exEntities.add(dupAliasEntity); } else { aliasMap.put(entity.getTableAlias(), entity); } } } for (ModelEntity modelEntity: mapByEntityName.values()) { Entity entity = modelEntity.getEntity(); if (entity.getTableName() != null && entity.getTableAlias() == null) { // determine the meaningful letters: Start and end of camel case words StringBuilder letters = new StringBuilder(); char lastChar = '_'; for (int i=0; i < entity.getName().length(); i++) { char currentChar = entity.getName().charAt(i); if (Character.isUpperCase(currentChar)) { letters.append(currentChar); } lastChar = currentChar; } if (letters.length() <= 1) { letters.append(lastChar); } String alias = letters.toString().toLowerCase(); if (aliasMap.get(alias) == null) { // found a new alias: check agains reserved word boolean aliasValid = true; for (Backend backend: getEntityFactory().getBackends()) { try { backend.assertValidName("table alias", alias); } catch (RuntimeException rex) { aliasValid = false; break; } } if (aliasValid) { ((EntityImpl) entity).setTableAlias(alias); aliasMap.put(alias, entity); continue; } } // just take the first letter and count. This will always work for (int count=1; ; count++) { String numberedAlias = alias.substring(0, 1) + count; if (aliasMap.get(numberedAlias) == null) { // found a new alias ((EntityImpl) entity).setTableAlias(numberedAlias); aliasMap.put(numberedAlias, entity); break; } } } } // sets the inheritance level for (ModelEntity modelEntity: mapByEntityName.values()) { Entity entity = modelEntity.getEntity(); int level = 0; Entity superEntity = entity.getSuperEntity(); while (superEntity != null) { level++; superEntity = superEntity.getSuperEntity(); } ((EntityImpl) entity).setInheritanceLevel(level); } // check hierarchies if (loadedEntity != null) { // check the hierarchy the loaded entity belongs to Entity topEntity = loadedEntity.getTopSuperEntity(); validateInheritanceHierarchy(topEntity, topEntity.getInheritanceType(), topEntity.getOptions().getTrackType(), topEntity.getIntegrity(), exMsg, exEntities); validateComponents(topEntity, topEntity.getIntegrity(), exMsg, exEntities); } else { // check that within each class hierarchy for (ModelEntity modelEntity: mapByEntityName.values()) { Entity entity = modelEntity.getEntity(); validateInheritanceHierarchy(entity, entity.getInheritanceType(), entity.getOptions().getTrackType(), entity.getIntegrity(), exMsg, exEntities); validateComponents(entity, entity.getIntegrity(), exMsg, exEntities); } } if (exMsg.length() > 0) { ModelException ex = new ModelException(exMsg.toString()); ex.getEntities().addAll(exEntities); throw ex; } } /** * Updates the composite path. * * @param compositePath the path that leads to given entity so far * @param entity the entity */ protected void updateCompositePath(List compositePath, Entity entity) { Relation lastRelation = compositePath.get(compositePath.size() - 1); if (lastRelation.getForeignAttribute() != null) { // the attribute actually may belong to a superclass entity = lastRelation.getForeignAttribute().getEntity(); } entity.getCompositePaths().add(compositePath); for (Relation relation: entity.getAllRelations()) { if (relation.isComposite()) { List newPath = new ArrayList<>(compositePath); newPath.add(relation); updateCompositePath(newPath, relation.getForeignEntity()); } } } @Override public synchronized Collection getForeignKeys() throws ModelException { if (foreignKeys == null) { Map foreignKeyMap = new TreeMap<>(); // map to update composite flag and sort StringBuilder exMsg = new StringBuilder(); Set exEntities = new HashSet<>(); for (ModelEntity modelEntity : mapByEntityName.values()) { Entity entity = modelEntity.getEntity(); if (entity.getInheritanceType().isMappingToOwnTable()) { for (Relation relation : entity.getTableRelations()) { Attribute referencingAttribute = null; Entity referencingEntity = null; Attribute referencedAttribute = null; Entity referencedEntity = null; boolean composite = relation.isComposite(); // valid for OBJECT relations if (!composite) { Relation foreignRelation = relation.getForeignRelation(); if (foreignRelation != null && foreignRelation.getRelationType() == RelationType.LIST && foreignRelation.isComposite()) { // the list relation is composite composite = true; } } if (relation.getAttribute() != null) { // object reference if (relation.getForeignEntity() == null) { if (exMsg.length() > 0) { exMsg.append('\n'); } exMsg.append("no foreign entity for "); exMsg.append(relation); exEntities.add(entity); } else { referencingEntity = relation.getEntity(); referencingAttribute = relation.getAttribute(); referencedEntity = relation.getForeignEntity(); referencedAttribute = referencedEntity.getAttributeByJavaName(Constants.AN_ID, true); } } else if (relation.getForeignAttribute() != null) { // list reference referencingEntity = relation.getForeignEntity(); referencingAttribute = relation.getForeignAttribute(); referencedEntity = entity; referencedAttribute = entity.getAttributeByJavaName(Constants.AN_ID, true); } else { if (exMsg.length() > 0) { exMsg.append('\n'); } exMsg.append("broken relation "); exMsg.append(relation); exMsg.append(" in "); exMsg.append(entity); exEntities.add(entity); } List referencingEntities; if (referencingEntity != null && referencingEntity.getTableProvidingEntity() == null) { // PLAIN referencingEntities = referencingEntity.getLeafEntities(); } else { referencingEntities = new ArrayList<>(); if (referencedEntity != null) { referencingEntities.add(referencingEntity); } } for (Entity refingEnt: referencingEntities) { if (referencingAttribute != null && referencedAttribute != null && (!composite && referencedAttribute.getEntity().getIntegrity().isRelatedCheckedByDatabase() || composite && referencedAttribute.getEntity().getIntegrity().isCompositesCheckedByDatabase())) { ForeignKey foreignKey = new ForeignKeyImpl(refingEnt, referencingAttribute, referencedEntity, referencedAttribute, composite); ForeignKey oppositeForeignKey = foreignKeyMap.get(foreignKey); if (oppositeForeignKey != null) { if (composite && !oppositeForeignKey.isComposite()) { // upgrade to composite ((ForeignKeyImpl) oppositeForeignKey).setComposite(true); } } else { // new foreignKeyMap.put(foreignKey, foreignKey); } } } } // add constraints for MULTI table inheritance if (entity.getSuperEntity() != null && entity.getHierarchyInheritanceType().isMappingToOwnTable()) { // is a subclass Entity topEntity = entity.getTopSuperEntity(); Attribute idAttribute = topEntity.getAttributeByJavaName(Constants.AN_ID, false); ForeignKey foreignKey = new ForeignKeyImpl(entity, idAttribute, topEntity, idAttribute, false); foreignKeyMap.put(foreignKey, foreignKey); } } } if (exMsg.length() > 0) { ModelException ex = new ModelException(exMsg.toString()); ex.getEntities().addAll(exEntities); throw ex; } foreignKeys = foreignKeyMap.values(); } return foreignKeys; } /** * Creates the entity factory for this model.
* The method is invoked once from within the Model constructor. * * @return the entity factory */ protected EntityFactory createEntityFactory() { return new EntityFactoryImpl(); } /** * Creates a reader for given filename. * * @param fileName the filename * @return the reader * @throws ModelException if creating the reader failed (because file not found) */ protected Reader createFileReader(String fileName) throws ModelException { try { return new FileReader(fileName); } catch (FileNotFoundException ex) { throw new ModelException("cannot open reader for " + fileName, ex); } } /** * Verifies that the inheritance, integrity and track type is the same within a given interitance hierarchy.
* Notice that concrete classes (the leafs of the hierarchy) must have InheritanceType.NONE defined! * * @param entity the entity to check * @param inheritanceType the inheritance type * @param trackType the tracking type * @param integrity the integrity type * @param exMsg execption message buffer * @param exEntities the entities related to the exception message * @return the error string, empty if all is ok */ protected String validateInheritanceHierarchy(Entity entity, InheritanceType inheritanceType, TrackType trackType, Integrity integrity, StringBuilder exMsg, Set exEntities) { if (entity.getOptions().getTrackType() != trackType) { if (exMsg.length() > 0) { exMsg.append('\n'); } exMsg.append(entity); exMsg.append(" has wrong track type "); exMsg.append(entity.getOptions().getTrackType()); exMsg.append(", expected "); exMsg.append(trackType); exEntities.add(entity); } if (entity.getIntegrity() != integrity) { if (exMsg.length() > 0) { exMsg.append('\n'); } exMsg.append(entity); exMsg.append(" has wrong integrity type "); exMsg.append(entity.getIntegrity()); exMsg.append(", expected "); exMsg.append(integrity); exEntities.add(entity); } if (entity.getSubEntities().isEmpty()) { if (entity.getInheritanceType() != InheritanceType.NONE) { if (exMsg.length() > 0) { exMsg.append('\n'); } exMsg.append(entity); exMsg.append(" without child entities must have an inheritance type of NONE"); exEntities.add(entity); } } else { if (entity.getInheritanceType() != inheritanceType) { if (exMsg.length() > 0) { exMsg.append('\n'); } exMsg.append(entity); exMsg.append(" has wrong inheritance type "); exMsg.append(entity.getInheritanceType()); exMsg.append(", expected "); exMsg.append(inheritanceType); exEntities.add(entity); } for (Entity child: entity.getSubEntities()) { validateInheritanceHierarchy(child, inheritanceType, trackType, integrity, exMsg, exEntities); } } return exMsg.toString(); } /** * Verifies that the integrity is the same within a given composite hierarchy.
* * @param entity the entity to check * @param integrity the integrity type * @param exMsg execption message buffer * @param exEntities the entities related to the exception message * @return the error string, empty if all is ok */ protected String validateComponents(Entity entity, Integrity integrity, StringBuilder exMsg, Set exEntities) { if (entity.getIntegrity() != integrity) { if (exMsg.length() > 0) { exMsg.append('\n'); } exMsg.append(entity); exMsg.append(" has wrong integrity type "); exMsg.append(entity.getIntegrity()); exMsg.append(", expected "); exMsg.append(integrity); exEntities.add(entity); } for (Relation relation: entity.getRelations()) { if (relation.isComposite()) { validateComponents(relation.getForeignEntity(), integrity, exMsg, exEntities); } } return exMsg.toString(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy