Package org.molgenis.model

Source Code of org.molgenis.model.MolgenisModelValidator

package org.molgenis.model;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Vector;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.molgenis.MolgenisOptions;
import org.molgenis.fieldtypes.EnumField;
import org.molgenis.fieldtypes.IntField;
import org.molgenis.fieldtypes.MrefField;
import org.molgenis.fieldtypes.StringField;
import org.molgenis.fieldtypes.TextField;
import org.molgenis.fieldtypes.XrefField;
import org.molgenis.framework.db.DatabaseException;
import org.molgenis.model.elements.Entity;
import org.molgenis.model.elements.Field;
import org.molgenis.model.elements.Model;
import org.molgenis.model.elements.Module;
import org.molgenis.model.elements.UISchema;
import org.molgenis.model.elements.Unique;
import org.molgenis.model.elements.View;
import org.molgenis.util.Pair;

public class MolgenisModelValidator
{
  private static final Logger logger = Logger.getLogger(MolgenisModelValidator.class.getSimpleName());

  public static void validate(Model model, MolgenisOptions options) throws MolgenisModelException, DatabaseException
  {
    logger.debug("validating model and adding defaults:");

    // validate the model
    validateNamesAndReservedWords(model, options);
    validateExtendsAndImplements(model);

    if (options.object_relational_mapping.equals(MolgenisOptions.SUBCLASS_PER_TABLE))
    {
      addTypeFieldInSubclasses(model);
    }

    validateKeys(model);
    addXrefLabelsToEntities(model);
    validatePrimaryKeys(model);
    validateForeignKeys(model);
    validateViews(model);
    validateOveride(model);

    // enhance the model
    correctXrefCaseSensitivity(model);
    // if(!options.mapper_implementation.equals(MolgenisOptions.MapperImplementation.JPA))
    // {
    moveMrefsFromInterfaceAndCopyToSubclass(model);
    createLinkTablesForMrefs(model, options);
    // }
    copyDefaultXrefLabels(model);
    copyDecoratorsToSubclass(model);

    if (options.object_relational_mapping.equals(MolgenisOptions.CLASS_PER_TABLE))
    {
      addInterfaces(model);
    }

    copyFieldsToSubclassToEnforceConstraints(model);

    validateNameSize(model, options);

  }

  /**
   * As mrefs are a linking table between to other tables, interfaces cannot
   * be part of mrefs (as they don't have a linking table). To solve this
   * issue, mrefs will be removed from interface class and copied to subclass.
   *
   * @throws MolgenisModelException
   */
  public static void moveMrefsFromInterfaceAndCopyToSubclass(Model model) throws MolgenisModelException
  {
    logger.debug("copy fields to subclass for constrain checking...");

    // copy mrefs from interfaces to implementing entities
    // also rename the target from interface to entity
    for (Entity entity : model.getEntities())
    {
      for (Entity iface : entity.getImplements())
      {
        for (Field mref : iface.getFieldsOf(new MrefField()))
        {
          Field f = new Field(mref);
          f.setEntity(entity);

          String mrefName = entity.getName() + "_" + f.getName();
          if (mrefName.length() > 30)
          {
            mrefName = mrefName.substring(0, 25) + Integer.toString(mrefName.hashCode()).substring(0, 5);
          }
          f.setMrefName(mrefName);
          entity.addField(0, f);
        }
      }
    }

    // remove interfaces from entities
    for (Entity entity : model.getEntities())
    {
      if (entity.isAbstract()) for (Field mref : entity.getFieldsOf(new MrefField()))
      {
        entity.removeField(mref);
      }
    }
  }

  /**
   * Subclasses can override fields of superclasses. This should only be used
   * with caution! Only good motivation is to limit xref type.
   */
  public static void validateOveride(Model model)
  {
    // TODO

  }

  public static void validateNameSize(Model model, MolgenisOptions options) throws MolgenisModelException
  {
    for (Entity e : model.getEntities())
    {
      // maximum num of chars in oracle table name of column is 30
      if (options.db_driver.toLowerCase().contains("oracle") && e.getName().length() > 30)
      {
        throw new MolgenisModelException(String.format("table name %s is longer than %d", e.getName(), 30));
      }
      for (Field f : e.getFields())
      {
        if (options.db_driver.toLowerCase().contains("oracle") && f.getName().length() > 30)
        {
          throw new MolgenisModelException(String.format("field name %s is longer than %d", f.getName(), 30));
        }
      }
    }
  }

  public static void validateUI(Model model, MolgenisOptions options) throws MolgenisModelException,
      DatabaseException
  {
    logger.debug("validating UI and adding defaults:");

    validateHideFields(model);
  }

  public static void validateHideFields(Model model) throws MolgenisModelException
  {
    for (org.molgenis.model.elements.Form form : model.getUserinterface().getAllForms())
    {
      List<String> hideFields = form.getHideFields();
      for (String fieldName : hideFields)
      {
        Entity entity = form.getEntity();
        Field field = entity.getAllField(fieldName);
        if (field == null)
        {
          throw new MolgenisModelException("error in hide_fields for form name=" + form.getName()
              + ": cannot find field '" + fieldName + "' in form entity='" + entity.getName() + "'");
        }
        else
        {
          if (!form.getReadOnly() && field.isNillable() == false && !field.isAuto()
              && field.getDefaultValue().equals(""))
          {

            logger.warn("you can get trouble with hiding field '" + fieldName + "' for form name="
                + form.getName()
                + ": record is not null and doesn't have a default value (unless decorator fixes this!");
          }
        }
      }
    }
  }

  public static void addXrefLabelsToEntities(Model model) throws MolgenisModelException
  {
    for (Entity e : model.getEntities())
    {
      if (e.getXrefLabels() == null)
      {
        // still empty then construct from secondary key
        List<String> result = new ArrayList<String>();
        if (e.getAllKeys().size() > 1)
        {
          for (Field f : e.getAllKeys().get(1).getFields())
            result.add(f.getName());
          e.setXrefLabels(result);
        }

        // otherwise use primary key
        else if (e.getAllKeys().size() > 0)
        {
          for (Field f : e.getAllKeys().get(0).getFields())
            result.add(f.getName());
          e.setXrefLabels(result);
        }

        logger.debug("added default xref_label=" + e.getXrefLabels() + " to entity=" + e.getName());

      }
    }

  }

  public static void validatePrimaryKeys(Model model) throws MolgenisModelException
  {
    for (Entity e : model.getEntities())
      if (!e.isAbstract())
      {
        if (e.getKeys().size() == 0) throw new MolgenisModelException("entity '" + e.getName()
            + " doesn't have a primary key defined ");
      }
  }

  /**
   * Default xref labels can come from: - the xref_entity (or one of its
   * superclasses)
   *
   * @param model
   * @throws MolgenisModelException
   */
  public static void copyDefaultXrefLabels(Model model) throws MolgenisModelException
  {
    for (Entity e : model.getEntities())
    {
      for (Field f : e.getFields())
      {
        if (f.getType() instanceof XrefField || f.getType() instanceof MrefField)
        {
          if (f.getXrefLabelNames().size() > 0 && f.getXrefLabelNames().get(0).equals(f.getXrefFieldName()))
          {
            Entity xref_entity = f.getXrefEntity();
            if (xref_entity.getXrefLabels() != null)
            {
              logger.debug("copying xref_label " + xref_entity.getXrefLabels() + " from "
                  + f.getXrefEntityName() + " to field " + f.getEntity().getName() + "."
                  + f.getName());
              f.setXrefLabelNames(xref_entity.getXrefLabels());
            }
          }
        }
      }
    }

  }

  /**
   * In each entity of an entity subclass hierarchy a 'type' field is added to
   * enable filtering. This method adds this type as 'enum' field such that
   * all subclasses are an enum option.
   *
   * @param model
   * @throws MolgenisModelException
   */
  public static void addTypeFieldInSubclasses(Model model) throws MolgenisModelException
  {
    logger.debug("add a 'type' field in subclasses to enable instanceof at database level...");
    for (Entity e : model.getEntities())
    {
      if (e.isRootAncestor())
      {
        Vector<Entity> subclasses = e.getAllDescendants();
        Vector<String> enumOptions = new Vector<String>();
        enumOptions.add(firstToUpper(e.getName()));
        for (Entity subclass : subclasses)
        {
          enumOptions.add(firstToUpper(subclass.getName()));
        }
        if (e.getField(Field.TYPE_FIELD) == null)
        {
          Field type_field = new Field(e, new EnumField(), Field.TYPE_FIELD, Field.TYPE_FIELD, true, false,
              true, null);
          type_field.setDescription("Subtypes have to be set to allow searching");
          // FIXME should be true, but breaks existing apps
          // type_field.setSystem(true);
          type_field.setHidden(true);
          e.addField(0, type_field);
        }
        e.getField(Field.TYPE_FIELD).setEnumOptions(enumOptions);
      }
      else
      {
        e.removeField(e.getField(Field.TYPE_FIELD));
      }
    }

  }

  /**
   * Add link tables for many to many relationships
   * <ul>
   * <li>A link table entity will have the name of [from_entity]_[to_entity]
   * <li>A link table has two xrefs to the from/to entity respectively
   * <li>The column names are those of the respective fields
   * <li>In case of a self reference, the second column name is '_self'
   * </ul>
   *
   * @param model
   * @throws MolgenisModelException
   */
  public static void createLinkTablesForMrefs(Model model, MolgenisOptions options) throws MolgenisModelException
  {
    logger.debug("add linktable entities for mrefs...");
    // find the multi-ref fields
    for (Entity xref_entity_from : model.getEntities())
    {

      // iterate through all fields including those inherited from
      // interfaces
      for (Field xref_field_from : xref_entity_from.getImplementedFieldsOf(new MrefField()))
      {
        try
        {
          // retrieve the references to the entity+field
          Entity xref_entity_to = xref_field_from.getXrefEntity();
          Field xref_field_to = xref_field_from.getXrefField();

          // TODO: check whether this link is already present

          // create the new entity for the link, if explicitly named
          String mref_name = xref_field_from.getMrefName(); // explicit

          // if mref_name longer than 30 throw error
          if (options.db_driver.toLowerCase().contains("oracle") && mref_name.length() > 30)
          {
            throw new MolgenisModelException("mref_name cannot be longer then 30 characters, found: "
                + mref_name);
          }

          // check if the mref already exists
          Entity mrefEntity = null;
          try
          {
            mrefEntity = model.getEntity(mref_name);
          }
          catch (Exception e)
          {
          }

          // if mref entity doesn't exist: create
          if (mrefEntity == null)
          {
            mrefEntity = new Entity(mref_name, mref_name, model.getDatabase());
            mrefEntity.setNamespace(xref_entity_from.getNamespace());
            mrefEntity.setAssociation(true);
            mrefEntity.setDescription("Link table for many-to-many relationship '"
                + xref_entity_from.getName() + "." + xref_field_from.getName() + "'.");
            mrefEntity.setSystem(true);

            // create id field to ensure ordering
            Field idField = new Field(mrefEntity, new IntField(), "autoid", "autoid", true, false, false,
                null);
            idField.setHidden(true);
            idField.setDescription("automatic id field to ensure ordering of mrefs");
            mrefEntity.addField(idField);
            mrefEntity.addKey(idField.getName(), "unique auto key to ensure ordering of mrefs");

            // create the fields for the linktable
            Field field;
            Vector<String> unique = new Vector<String>();

            field = new Field(mrefEntity, new XrefField(), xref_field_from.getMrefRemoteid(), null, false,
                false, false, null);
            field.setXRefVariables(xref_entity_to.getName(), xref_field_to.getName(),
                xref_field_from.getXrefLabelNames());
            if (xref_field_from.isXrefCascade()) field.setXrefCascade(true);
            mrefEntity.addField(field);

            unique.add(field.getName());

            // add all the key-fields of xref_entity_from
            for (Field key : xref_entity_from.getKeyFields(Entity.PRIMARY_KEY))
            {
              field = new Field(mrefEntity, new XrefField(), xref_field_from.getMrefLocalid(), null,
                  false, false, false, null);

              // null xreflabel
              field.setXRefVariables(xref_entity_from.getName(), key.getName(), null);

              mrefEntity.addField(field);
              unique.add(field.getName());
            }

            // create the unique combination
            mrefEntity.addKey(unique, false, null);

          }
          // if mrefEntity does not exist, check xref_labels
          else
          {
            // field is xref_field, does it have label(s)?
            Field xrefField = mrefEntity.getAllField(xref_field_to.getName());

            // verify xref_label
            if (xrefField != null)
            {
              // logger.debug("adding xref_label "+xref_field_to.getXrefLabelNames()+"'back' for "+xrefField.getName());
              xrefField.setXrefLabelNames(xref_field_from.getXrefLabelNames());

            }
          }

          // set the linktable reference in the xref-field
          xref_field_from.setMrefName(mrefEntity.getName());
        }
        catch (Exception e)
        {
          e.printStackTrace();
          System.exit(-1);
        }
      }
    }

  }

  /**
   * Check if the view objects are an aggregate of known entities.
   *
   * @param model
   * @throws MolgenisModelException
   */
  public static void validateViews(Model model) throws MolgenisModelException
  {
    // validate the views
    for (View view : model.getViews())
    {
      Vector<Entity> entities = new Vector<Entity>();
      Vector<Pair<Entity, Entity>> references = new Vector<Pair<Entity, Entity>>();

      // retrieve all the entities
      for (String viewentity : view.getEntities())
      {
        Entity entity = model.getEntity(viewentity);
        if (entity == null) throw new MolgenisModelException("Entity '" + viewentity + "' in view '"
            + view.getName() + "' does not exist");

        entities.add(entity);
      }

      // validate that there are xref's pointing to the respective
      // entities
      for (Entity entity : entities)
      {
        for (Field field : entity.getFields())
        {
          if (!(field.getType() instanceof XrefField)) continue;

          // get the entity, which is referenced by the field
          Entity referenced = null;
          try
          {
            referenced = field.getXrefEntity();
          }
          catch (Exception e)
          {
            ;
          }

          // check whether we're referencing one of the other entities
          // in the view
          for (Entity other : entities)
          {
            // exclude ourselves
            if (other.getName().equals(entity.getName())) continue;

            // check whether this is an entity we're referencing
            if (other.getName().equals(referenced.getName())) references.add(new Pair<Entity, Entity>(
                entity, other));
          }
        }
      }

      // if the sizes are not equal, then we could not link up all the
      // entities
      Vector<Entity> viewentities = new Vector<Entity>();
      for (Pair<Entity, Entity> p : references)
      {
        if (!viewentities.contains(p.getA())) viewentities.add(p.getA());
        if (!viewentities.contains(p.getB())) viewentities.add(p.getB());
      }

      // if (viewentities.size() != view.getEntities().size())
      // throw new DSLParseException("Cannot link up all the entities in "
      // + " view " + view.getName());
    }

  }

  /**
   * Validate foreign key relationships: <li>
   * <ul>
   * Do the xref_field and xref_label refer to fields actually exist
   * <ul>
   * Is the entity refered to non-abstract
   * <ul>
   * Does the xref_field refer to a unique field (i.e. foreign key)</li>
   *
   * @param model
   * @throws MolgenisModelException
   * @throws DatabaseException
   */
  public static void validateForeignKeys(Model model) throws MolgenisModelException, DatabaseException
  {
    logger.debug("validate xref_field and xref_label references...");

    // validate foreign key relations
    for (Entity entity : model.getEntities())
    {
      String entityname = entity.getName();

      for (Field field : entity.getFields())
      {
        String fieldname = field.getName();
        if (field.getType() instanceof XrefField || field.getType() instanceof MrefField)
        {

          String xref_entity_name = field.getXrefEntityName();
          String xref_field_name = field.getXrefFieldName();

          List<String> xref_label_names = field.getXrefLabelNames();

          // if no secondary key, use primary key
          if (xref_label_names.size() == 0)
          {
            xref_label_names.add(field.getXrefFieldName());
          }

          Entity xref_entity = model.getEntity(xref_entity_name);
          if (xref_entity == null) throw new MolgenisModelException("xref entity '" + xref_entity_name
              + "' does not exist for field " + entityname + "." + fieldname);

          if (xref_field_name == null || xref_field_name.equals(""))
          {
            xref_field_name = xref_entity.getPrimaryKey().getName();
            field.setXrefField(xref_field_name);

            logger.debug("automatically set " + entityname + "." + fieldname + " xref_field="
                + xref_field_name);
          }

          if (!xref_entity.getName().equals(field.getXrefEntityName())) throw new MolgenisModelException(
              "xref entity '" + xref_entity_name + "' does not exist for field " + entityname + "."
                  + fieldname + " (note: entity names are case-sensitive)");

          if (xref_entity.isAbstract())
          {
            throw new MolgenisModelException("cannot refer to abstract xref entity '" + xref_entity_name
                + "' from field " + entityname + "." + fieldname);
          }

          // if (entity.isAbstract()
          // && field.getType() instanceof MrefField) throw new
          // MolgenisModelException(
          // "interfaces cannot have mref therefore remove '"
          // + entityname + "." + fieldname + "'");

          Field xref_field = xref_entity.getField(xref_field_name, false, true, true);

          if (xref_field == null) throw new MolgenisModelException("xref field '" + xref_field_name
              + "' does not exist for field " + entityname + "." + fieldname);

          // if (xref_field == null) xref_field =
          // xref_entity.getPrimaryKey();
          // throw new MolgenisModelException("xref field '" +
          // xref_field_name
          // + "' does not exist for field " + entityname + "." +
          // fieldname);

          for (String xref_label_name : xref_label_names)
          {
            Field xref_label = null;
            // test if label is defined as {entity}.{field}
            if (xref_label_name.contains("."))
            {
              xref_label = model.findField(xref_label_name);
            }
            // else assume {entity} == xref_entity
            else
            {
              xref_label = xref_entity.getAllField(xref_label_name);
            }
            // if null, check if a path to another xref_label:
            // 'fieldname_xreflabel'
            if (xref_label == null)
            {
              StringBuilder validFieldsBuilder = new StringBuilder();
              Map<String, List<Field>> candidates = field.allPossibleXrefLabels();

              if (candidates.size() == 0)
              {
                throw new MolgenisModelException(
                    "xref label '"
                        + xref_label_name
                        + "' does not exist for field "
                        + entityname
                        + "."
                        + fieldname
                        + ". \nCouldn't find suitable secondary keys to use as xref_label. \nDid you set a unique=\"true\" or <unique fields=\" ...>?");
              }

              for (Entry<String, List<Field>> entry : candidates.entrySet())
              {
                String key = entry.getKey();
                if (xref_label_name.equals(key))
                {
                  List<Field> value = entry.getValue();
                  xref_label = value.get(value.size() - 1);
                }
                validFieldsBuilder.append(',').append(key);
              }

              // still null, must be error
              if (xref_label == null)
              {
                throw new MolgenisModelException("xref label '" + xref_label_name
                    + "' does not exist for field " + entityname + "." + fieldname
                    + ". Valid labels include " + validFieldsBuilder.toString());
              }

            }
            else
            {
              // validate the label

              if (!xref_label_name.equals(xref_field_name)
                  && !field.allPossibleXrefLabels().keySet().contains(xref_label_name))
              {
                String validLabels = StringUtils.join(field.allPossibleXrefLabels().keySet(), ',');
                throw new MolgenisModelException("xref label '" + xref_label_name + "' for "
                    + entityname + "." + fieldname
                    + " is not part a secondary key. Valid labels are " + validLabels
                    + "\nDid you set a unique=\"true\" or <unique fields=\" ...>?");
              }

            }

          }

          if (xref_field.getType() instanceof TextField) throw new MolgenisModelException("xref field '"
              + xref_field_name + "' is of illegal type 'TEXT' for field " + entityname + "." + fieldname);

          boolean isunique = false;
          for (Unique unique : xref_entity.getAllKeys())
          {
            for (Field keyfield : unique.getFields())
            {
              if (keyfield.getName().equals(xref_field_name)) isunique = true;
            }
          }
          if (!isunique) throw new MolgenisModelException("xref pointer '" + xref_entity_name + "."
              + xref_field_name + "' is a non-unique field for field " + entityname + "." + fieldname
              + "\n" + xref_entity.toString());
        }
      }
    }
  }

  /**
   * Validate the unique constraints
   * <ul>
   * <li>Do unique field names refer to existing fields?
   * <li>Is there a unique column id + unique label?
   * </ul>
   *
   * @param model
   * @throws MolgenisModelException
   */
  public static void validateKeys(Model model) throws MolgenisModelException
  {
    logger.debug("validate the fields used in 'unique' constraints...");
    // validate the keys
    for (Entity entity : model.getEntities())
    {
      String entityname = entity.getName();
      int autocount = 0;
      for (Field field : entity.getAllFields())
      {
        String fieldname = field.getName();
        if (field.isAuto() && field.getType() instanceof IntField)
        {
          autocount++;

          boolean iskey = false;

          for (Unique unique : entity.getAllKeys())
          {
            for (Field keyfield : unique.getFields())
            {
              if (keyfield.getName() == null) throw new MolgenisModelException("unique field '"
                  + fieldname + "' is not known in entity " + entityname);
              if (keyfield.getName().equals(field.getName())) iskey = true;
            }
          }

          if (!iskey) throw new MolgenisModelException(
              "there can be only one auto column and it must be the primary key for field '" + entityname
                  + "." + fieldname + "'");
        }

        if (field.getType() instanceof EnumField)
        {
          if (field.getDefaultValue() != null && !"".equals(field.getDefaultValue())) if (!field
              .getEnumOptions().contains(field.getDefaultValue()))
          {
            throw new MolgenisModelException("default value '" + field.getDefaultValue()
                + "' is not in enum_options for field '" + entityname + "." + fieldname + "'");
          }
        }
      }

      if (autocount > 1) throw new MolgenisModelException(
          "there should be only one auto column and it must be the primary key for entity '" + entityname
              + "'");

      // to strict, the unique field may be non-automatic
      if (!entity.isAbstract() && autocount < 1)
      {
        throw new MolgenisModelException(
            "there should be one auto column for each root entity and it must be the primary key for entity '"
                + entityname + "'");
      }
    }

  }

  /**
   * Validate extends and implements relationships:
   * <ul>
   * <li>Do superclasses actually exist
   * <li>Do 'implements' refer to abstract superclasses (interfaces)
   * <li>Do 'extends' refer to non-abstract superclasses
   * <li>Copy primary key to subclass to form parent/child relationships
   * </ul>
   *
   * @param model
   * @throws MolgenisModelException
   */
  public static void validateExtendsAndImplements(Model model) throws MolgenisModelException
  {
    logger.debug("validate 'extends' and 'implements' relationships...");
    // validate the extends and implements relations
    for (Entity entity : model.getEntities())
    {

      List<Entity> ifaces = entity.getAllImplements();
      for (Entity iface : ifaces)
      {
        if (!iface.isAbstract()) throw new MolgenisModelException(entity.getName() + " cannot implement "
            + iface.getName() + " because it is not abstract");

        // copy primary key and xref_label from interface to subclass,
        // a primary key can have only one field.
        // usually it is a auto_number int
        // composite keys are ignored
        try
        {
          Field pkeyField = null;
          if (iface.getKeyFields(Entity.PRIMARY_KEY).size() == 1)
          {
            pkeyField = iface.getKeyFields(Entity.PRIMARY_KEY).get(0);
            // if not already exists
            if (entity.getField(pkeyField.getName()) == null)
            {
              Field field = new Field(pkeyField);
              field.setEntity(entity);
              field.setAuto(pkeyField.isAuto());
              field.setNillable(pkeyField.isNillable());
              field.setReadonly(pkeyField.isReadOnly());
              field.setXRefVariables(iface.getName(), pkeyField.getName(), null);
              field.setHidden(true);

              logger.debug("copy primary key " + field.getName() + " from interface " + iface.getName()
                  + " to " + entity.getName());
              entity.addField(field);

            }
          }

        }
        catch (Exception e)
        {
          e.printStackTrace();
          throw new MolgenisModelException(e.getMessage());
        }

      }

      Vector<String> parents = entity.getParents();
      if (parents.size() != 0)
      {
        Entity parent = model.getEntity(parents.get(0));
        if (parent == null) throw new MolgenisModelException("superclass '" + parents.get(0) + "' for '"
            + entity.getName() + "' is missing");
        if (parent.isAbstract()) throw new MolgenisModelException(entity.getName() + " cannot extend "
            + parents.get(0) + " because superclas " + parents.get(0) + " is abstract (use implements)");
        if (entity.isAbstract()) throw new MolgenisModelException(entity.getName() + " cannot extend "
            + parents.get(0) + " because " + entity.getName() + " itself is abstract");

        if (parent.getKeys().size() == 0)
        {
          // log.out("panix");
          continue;

        }

        // copy primary key from superclass to subclass
        // try
        // {
        // Vector<String> keys = new Vector<String>();
        // for (Field key : parent.getKeyFields(Entity.PRIMARY_KEY))
        // {
        // if (entity.getField(key.getName()) == null)
        // {
        // Field field = new Field(key);
        // field.setEntity(entity);
        // field.setAuto(key.isAuto());
        // field.setNillable(key.isNillable());
        // field.setReadonly(key.isReadOnly());
        //
        // field.setSystem(true);
        // field.setXRefVariables(parent.getName(), key.getName(),
        // null);
        // field.setHidden(true);
        //
        // entity.addField(field);
        // logger.debug("copy primary key " + field.getName() +
        // " from superclass " + parent.getName()
        // + " to " + entity.getName());
        // keys.add(field.getName());
        // }
        // }
        // if (keys.size() > 0) entity.getKeys().add(0,
        // new Unique(entity, keys, false,
        // "unique reference to superclass"));
        // }
        // catch (Exception e)
        // {
        // throw new MolgenisModelException(e.getMessage());
        // }
      }
    }
  }

  /**
   * Add interfaces as artificial entities to the model
   *
   * @param model
   * @throws MolgenisModelException
   * @throws Exception
   */
  public static void addInterfaces(Model model) throws MolgenisModelException
  {
    logger.debug("add root entities for interfaces...");
    for (Entity entity : model.getEntities())
    {
      // Generate the interface if rootAncestor (so has subclasses) and
      // itself is not an interface...
      if (entity.isRootAncestor())
      {
        Entity rootAncestor = entity;
        if (!entity.isAbstract())
        {

          // generate a new interface
          rootAncestor = new Entity("_" + entity.getName() + "Interface", entity.getName(),
              model.getDatabase());
          rootAncestor
              .setDescription("Identity map table for "
                  + entity.getName()
                  + " and all its subclasses. "
                  + "For each row that is added to "
                  + entity.getName()
                  + " or one of its subclasses, first a row must be added to this table to get a valid primary key value.");
          // rootAncestor.setAbstract( true );

          // copy key fields to interface and unset auto key in child
          Vector<Field> keyfields = entity.getKey(0).getFields();
          Vector<String> keyfields_copy = new Vector<String>();
          for (Field f : keyfields)
          {
            Field key_field = new Field(rootAncestor, f.getType(), f.getName(), f.getName(), f.isAuto(),
                f.isNillable(), f.isReadOnly(), f.getDefaultValue());
            key_field.setDescription("Primary key field unique in " + entity.getName()
                + " and its subclasses.");
            if (key_field.getType() instanceof StringField) key_field.setVarCharLength(key_field
                .getVarCharLength());
            rootAncestor.addField(key_field);
            keyfields_copy.add(key_field.getName());

            if (f.isAuto())
            {
              // unset auto key in original, but

              // SOLVED BY TRIGGERS Field autoField =
              // entity.getField(f.getName());
              // SOLVED BY TRIGGERS autoField.setAuto(false);

            }
          }
          rootAncestor.addKey(keyfields_copy, entity.getKey(0).isSubclass(), null);

          Vector<String> parents = new Vector<String>();
          parents.add(rootAncestor.getName());
          entity.setParents(parents);
        }

        // add the type enum to the root element
        Vector<Entity> subclasses = entity.getAllDescendants();
        Vector<String> enumOptions = new Vector<String>();
        enumOptions.add(entity.getName());
        for (Entity subclass : subclasses)
        {
          enumOptions.add(subclass.getName());
        }
        Field type_field = new Field(rootAncestor, new EnumField(), Field.TYPE_FIELD, Field.TYPE_FIELD, true,
            false, false, null);
        type_field.setDescription("Subtypes of " + entity.getName() + ". Have to be set to allow searching");
        type_field.setEnumOptions(enumOptions);
        type_field.setHidden(true);
        rootAncestor.addField(0, type_field);
      }
    }
  }

  public static void validateNamesAndReservedWords(Model model, MolgenisOptions options)
      throws MolgenisModelException
  {
    logger.debug("check for JAVA and SQL reserved words...");
    List<String> keywords = new ArrayList<String>();
    // keywords.addAll(Arrays.asList(MOLGENIS_KEYWORDS));
    keywords.addAll(Arrays.asList(JAVA_KEYWORDS));
    keywords.addAll(Arrays.asList(JAVASCRIPT_KEYWORDS));
    keywords.addAll(Arrays.asList(ORACLE_KEYWORDS));

    if (options.db_driver.contains("mysql")) keywords.addAll(Arrays.asList(MYSQL_KEYWORDS));
    if (options.db_driver.contains("hsql")) keywords.addAll(Arrays.asList(HSQL_KEYWORDS));

    if (model.getName().contains(" "))
    {
      throw new MolgenisModelException("model name '" + model.getName()
          + "' illegal: it cannot contain spaces. Use 'label' if you want to show a name with spaces.");
    }

    // if(!containsOnlyLetters(model.getName()))
    // {
    // throw new MolgenisModelException("model name '" + model.getName()
    // + "' illegal: it can only contain letters, no numbers or dots");
    // }

    for (Module m : model.getModules())
    {
      if (m.getName().contains(" "))
      {
        throw new MolgenisModelException("module name '" + m.getName()
            + "' illegal: it cannot contain spaces. Use 'label' if you want to show a name with spaces.");
      }
      // if(!containsOnlyLetters(m.getName()))
      // {
      // throw new MolgenisModelException("module name '" + m.getName()
      // + "' illegal: it can only contain letters, no numbers or dots");
      // }

    }

    for (Entity e : model.getEntities())
    {
      if (e.getName().contains(" "))
      {
        throw new MolgenisModelException("entity name '" + e.getName()
            + "' cannot contain spaces. Use 'label' if you want to show a name with spaces.");
      }

      if (keywords.contains(e.getName().toUpperCase()) || keywords.contains(e.getName().toLowerCase()))
      {
        // e.setName(e.getName() + "_");
        // logger.warn("entity name '" + e.getName() + "' illegal:" +
        // e.getName() + " is a reserved word");
        throw new MolgenisModelException("entity name '" + e.getName() + "' illegal:" + e.getName()
            + " is a reserved JAVA and/or SQL word and cannot be used for entity name");
      }
      for (Field f : e.getFields())
      {
        if (f.getName().contains(" "))
        {
          throw new MolgenisModelException("field name '" + e.getName() + "." + f.getName()
              + "' cannot contain spaces. Use 'label' if you want to show a name with spaces.");
        }

        if (keywords.contains(f.getName().toUpperCase()) || keywords.contains(f.getName().toLowerCase()))
        {
          // f.setName(f.getName() + "_");
          // logger.warn("field name '" + f.getName() + "' illegal:" +
          // f.getName() + " is a reserved word");
          throw new MolgenisModelException("field name '" + e.getName() + "." + f.getName() + "' illegal: "
              + f.getName() + " is a reserved JAVA and/or SQL word");
        }

        if (f.getType() instanceof XrefField || f.getType() instanceof MrefField)
        {
          String xref_entity = f.getXrefEntityName();
          if (xref_entity != null
              && (keywords.contains(xref_entity.toUpperCase()) || keywords.contains(xref_entity
                  .toLowerCase())))
          {
            // f.setXRefEntity(f.getXRefEntity() + "_");
            // logger.warn("field.xref-entity name '" + xref_entity
            // + "' illegal:" + xref_entity
            // + " is a reserved word");
            throw new MolgenisModelException("xref_entity reference from field '" + e.getName() + "."
                + f.getName() + "' illegal: " + xref_entity + " is a reserved JAVA and/or SQL word");
          }

          if (f.getType() instanceof MrefField)
          {
            // default mref name is entityname+"_"+xreffieldname
            if (f.getMrefName() == null)
            {
              String mrefEntityName = f.getEntity().getName() + "_" + f.getName();

              // check if longer than 30 characters, then truncate
              if (mrefEntityName.length() > 30)
              {
                mrefEntityName = mrefEntityName.substring(0, 25)
                    + Integer.toString(mrefEntityName.hashCode()).substring(0, 5);
              }

              // paranoia check on uniqueness
              Entity mrefEntity = null;
              try
              {
                mrefEntity = model.getEntity(mrefEntityName);
              }
              catch (Exception exc)
              {
                throw new MolgenisModelException("mref name for " + f.getEntity().getName() + "."
                    + f.getName() + " not unique. Please use explicit mref_name=name setting");
              }

              if (mrefEntity != null)
              {
                mrefEntityName += "_mref";
                if (model.getEntity(mrefEntityName) != null)
                {
                  mrefEntityName += "_" + Math.random();
                }
              }

              f.setMrefName(mrefEntityName);
            }
            if (f.getMrefLocalid() == null)
            {
              // default to entity name
              f.setMrefLocalid(f.getEntity().getName());
            }
            if (f.getMrefRemoteid() == null)
            {
              // default to xref entity name
              f.setMrefRemoteid(f.getName());
            }
          }
        }
      }
    }

    for (UISchema screen : model.getUserinterface().getAllChildren())
    {
      if (screen.getName().contains(" "))
      {
        throw new MolgenisModelException(
            "ui element '"
                + screen.getName()
                + "illegal: it cannot contain spaces. If you want to have a name with spaces use the 'label' attribute");
      }
    }
  }

  /** test for case sensitivity */
  public static void correctXrefCaseSensitivity(Model model) throws MolgenisModelException
  {
    logger.debug("correct case of names in xrefs...");
    for (Entity e : model.getEntities())
    {
      for (Field f : e.getFields())
      {
        // f.setName(f.getName().toLowerCase());

        if (f.getType() instanceof XrefField || f.getType() instanceof MrefField)
        {
          try
          {
            // correct for uppercase/lowercase typo's
            Entity xrefEntity = f.getXrefEntity();
            f.setXRefEntity(xrefEntity.getName());

            String xrefField = f.getXrefField().getName();

            List<String> xrefLabels = f.getXrefLabelsTemp();
            List<String> correctedXrefLabels = new ArrayList<String>();
            for (String xrefLabel : xrefLabels)
            {
              correctedXrefLabels.add(xrefEntity.getAllField(xrefLabel).getName());
            }
            f.setXRefVariables(xrefEntity.getName(), xrefField, correctedXrefLabels);
          }
          catch (Exception exception)
          {
            // exception.printStackTrace();
            // logger.error(exception);
          }
        }
      }
    }
  }

  /**
   *
   * @param model
   * @throws MolgenisModelException
   */
  public static void copyDecoratorsToSubclass(Model model) throws MolgenisModelException
  {
    logger.debug("copying decorators to subclasses...");
    for (Entity e : model.getEntities())
    {
      if (e.getDecorator() == null)
      {
        for (Entity superClass : e.getImplements())
        {
          if (superClass.getDecorator() != null)
          {
            e.setDecorator(superClass.getDecorator());
          }
        }
        for (Entity superClass : e.getAllAncestors())
        {
          if (superClass.getDecorator() != null)
          {
            e.setDecorator(superClass.getDecorator());
          }
        }
      }
    }
  }

  /**
   * Copy fields to subclasses (redundantly) so this field can be part of an
   * extra constraint. E.g. a superclass has non-unique field 'name'; in the
   * subclass it is said to be unique and a copy is made to capture this
   * constraint in the table for the subclass.
   *
   * @param model
   * @throws MolgenisModelException
   */
  public static void copyFieldsToSubclassToEnforceConstraints(Model model) throws MolgenisModelException
  {
    logger.debug("copy fields to subclass for constrain checking...");
    for (Entity e : model.getEntities())
    {
      // copy keyfields to subclasses to ensure that keys can be
      // enforced (if the key includes superclass fields).
      if (e.hasAncestor())
      {
        for (Unique aKey : e.getKeys())
        {
          for (Field f : aKey.getFields())
          {
            if (e.getField(f.getName()) == null)
            {
              // copy the field
              Field copy = new Field(f);
              copy.setEntity(e);
              copy.setAuto(f.isAuto());
              e.addField(copy);

              logger.debug(aKey.toString() + " cannot be enforced on " + e.getName() + ", copying "
                  + f.getEntity().getName() + "." + f.getName() + " to subclass as " + copy.getName());
            }
          }
        }

      }
    }
  }

  private static final String[] MOLGENIS_KEYWORDS =
  { "entity", "field", "form", "menu", "screen", "plugin" };

  private static final String[] HSQL_KEYWORDS =
  { "ALIAS", "ALTER", "AUTOCOMMIT", "CALL", "CHECKPOINT", "COMMIT", "CONNECT", "CREATE", "COLLATION", "COUNT",
      "DATABASE", "DEFRAG", "DELAY", "DELETE", "DISCONNECT", "DROP", "END", "EXPLAIN", "EXTRACT", "GRANT",
      "IGNORECASE", "INDEX", "INSERT", "INTEGRITY", "LOGSIZE", "PASSWORD", "POSITION", "PLAN", "PROPERTY",
      "READONLY", "REFERENTIAl", "REVOKE", "ROLE", "ROLLBACK", "SAVEPOINT", "SCHEMA", "SCRIPT", "SCRIPTFORMAT",
      "SELECT", "SEQUENCE", "SET", "SHUTDOWN", "SOURCE", "TABLE", "TRIGGER", "UPDATE", "USER", "VIEW", "WRITE" };
  /**
   * http://dev.mysql.com/doc/refman/5.0/en/reserved-words.html
   */
  private static final String[] MYSQL_KEYWORDS =
  { "Type", "ADD", "ALL", "ALTER", "ANALYZE", "AND", "AS", "ASC", "ASENSITIVE", "BEFORE", "BETWEEN", "BIGINT",
      "BINARY", "BLOB", "BOTH", "BY", "CALL", "CASCADE", "CASE", "CHANGE", "CHAR", "CHARACTER", "CHECK",
      "COLLATE", "COLUMN", "CONDITION", "CONNECTION", "CONSTRAINT", "CONTINUE", "CONVERT", "CREATE", "CROSS",
      "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP", "CURRENT_USER", "CURSOR", "DATABASE", "DATABASES",
      "DAY_HOUR", "DAY_MICROSECOND", "DAY_MINUTE", "DAY_SECOND", "DEC", "DECIMAL", "DECLARE", "DEFAULT",
      "DELAYED", "DELETE", "DESC", "DESCRIBE", "DETERMINISTIC", "DISTINCT", "DISTINCTROW", "DIV", "DOUBLE",
      "DROP", "DUAL", "EACH", "ELSE", "ELSEIF", "ENCLOSED", "ESCAPED", "EXISTS", "EXIT", "EXPLAIN", "FALSE",
      "FETCH", "FLOAT", "FLOAT4", "FLOAT8", "FOR", "FORCE", "FOREIGN", "FROM", "FULLTEXT", "GRANT", "GROUP",
      "HAVING", "HIGH_PRIORITY", "HOUR_MICROSECOND", "HOUR_MINUTE", "HOUR_SECOND", "IF", "IGNORE", "IN", "INDEX",
      "INFILE", "INNER", "INOUT", "INSENSITIVE", "INSERT", "INT", "INT1", "INT2", "INT3", "INT4", "INT8",
      "INTEGER", "INTERVAL", "INTO", "IS", "ITERATE", "JOIN", "KEY", "KEYS", "KILL", "LEADING", "LEAVE", "LEFT",
      "LIKE", "LIMIT", "LINES", "LOAD", "LOCALTIME", "LOCALTIMESTAMP", "LOCK", "LONG", "LONGBLOB", "LONGTEXT",
      "LOOP", "LOW_PRIORITY", "MATCH", "MEDIUMBLOB", "MEDIUMINT", "MEDIUMTEXT", "MIDDLEINT",
      "MINUTE_MICROSECOND", "MINUTE_SECOND", "MOD", "MODIFIES", "NATURAL", "NOT", "NO_WRITE_TO_BINLOG", "NULL",
      "NUMERIC", "ON", "OPTIMIZE", "OPTION", "OPTIONALLY", "OR", "ORDER", "OUT", "OUTER", "OUTFILE", "PRECISION",
      "PRIMARY", "PROCEDURE", "PURGE", "RAID0", "READ", "READS", "REAL", "REFERENCES", "REGEXP", "RELEASE",
      "RENAME", "REPEAT", "REPLACE", "REQUIRE", "RESTRICT", "RETURN", "REVOKE", "RIGHT", "RLIKE", "SCHEMA",
      "SCHEMAS", "SECOND_MICROSECOND", "SELECT", "SENSITIVE", "SEPARATOR", "SET", "SHOW", "SMALLINT", "SONAME",
      "SPATIAL", "SPECIFIC", "SQL", "SQLEXCEPTION", "SQLSTATE", "SQLWARNING", "SQL_BIG_RESULT",
      "SQL_CALC_FOUND_ROWS", "SQL_SMALL_RESULT", "SSL", "STARTING", "STRAIGHT_JOIN", "TABLE", "TERMINATED",
      "THEN", "TINYBLOB", "TINYINT", "TINYTEXT", "TO", "TRAILING", "TRIGGER", "TRUE", "UNDO", "UNION", "UNIQUE",
      "UNLOCK", "UNSIGNED", "UPDATE", "USAGE", "USE", "USING", "UTC_DATE", "UTC_TIME", "UTC_TIMESTAMP", "VALUES",
      "VARBINARY", "VARCHAR", "VARCHARACTER", "VARYING", "WHEN", "WHERE", "WHILE", "WITH", "WRITE", "X509",
      "XOR", "YEAR_MONTH", "ZEROFILL" };
  /**
   * https://cis.med.ucalgary.ca/http/java.sun.com/docs/books/tutorial/java/
   * nutsandbolts/_keywords.html
   */
  private static final String[] JAVA_KEYWORDS =
  { "abstract", "continue", "for", "new", "switch", "assert", "default", "goto", "package", "synchronized",
      "boolean", "do", "if", "private", "this", "break", "double", "implements", "protected", "throw", "byte",
      "else", "import", "public", "throws", "case", "enum", "instanceof", "return", "transient", "catch",
      "extends", "int", "short", "try", "char", "final", "interface", "static", "void", "class", "finally",
      "long", "strictfp", "volatile", "const", "float", "native", "super", "while" };

  private static final String[] JAVASCRIPT_KEYWORDS =
  { "function" };

  private static final String[] ORACLE_KEYWORDS =
  {

  "ACCESS", "ELSE", "MODIFY", "START", "ADD", "EXCLUSIVE", "NOAUDIT", "SELECT", "ALL", "EXISTS", "NOCOMPRESS",
      "SESSION", "ALTER", "FILE", "NOT", "SET", "AND", "FLOAT", "NOTFOUND", "SHARE", "ANY", "FOR", "NOWAIT",
      "SIZE", "ARRAYLEN", "FROM", "NULL", "SMALLINT", "AS", "GRANT", "NUMBER", "SQLBUF", "ASC", "GROUP", "OF",
      "SUCCESSFUL", "AUDIT", "HAVING", "OFFLINE", "SYNONYM", "BETWEEN", "IDENTIFIED", "ON", "SYSDATE", "BY",
      "IMMEDIATE", "ONLINE", "TABLE", "CHAR", "IN", "OPTION", "THEN", "CHECK", "INCREMENT", "OR", "TO",
      "CLUSTER", "INDEX", "ORDER", "TRIGGER", "COLUMN", "INITIAL", "PCTFREE", "UID", "COMMENT", "INSERT",
      "PRIOR", "UNION", "COMPRESS", "INTEGER", "PRIVILEGES", "UNIQUE", "CONNECT", "INTERSECT", "PUBLIC",
      "UPDATE", "CREATE", "INTO", "RAW", "USER", "CURRENT", "IS", "RENAME", "VALIDATE", "DATE", "LEVEL",
      "RESOURCE", "VALUES", "DECIMAL", "LIKE", "REVOKE", "VARCHAR", "DEFAULT", "LOCK", "ROW", "VARCHAR2",
      "DELETE", "LONG", "ROWID", "VIEW", "DESC", "MAXEXTENTS", "ROWLABEL", "WHENEVER", "DISTINCT", "MINUS",
      "ROWNUM", "WHERE", "DROP", "MODE", "ROWS", "WITH" };

  private static String firstToUpper(String string)
  {
    if (string == null) return " NULL ";
    if (string.length() > 0) return string.substring(0, 1).toUpperCase() + string.substring(1);
    else
      return " ERROR[STRING EMPTY] ";
  }

}
TOP

Related Classes of org.molgenis.model.MolgenisModelValidator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.