content: /**
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you 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
   * <p>
   * http://www.apache.org/licenses/LICENSE-2.0
   * <p>
   * 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.apache.atlas.type;
  
  import org.apache.atlas.AtlasErrorCode;
  import org.apache.atlas.exception.AtlasBaseException;
  import org.apache.atlas.model.TimeBoundary;
  import org.apache.atlas.model.instance.AtlasClassification;
  import org.apache.atlas.model.typedef.AtlasBaseTypeDef;
  import org.apache.atlas.model.typedef.AtlasClassificationDef;
  import org.apache.atlas.model.typedef.AtlasStructDef.AtlasAttributeDef;
  import org.apache.commons.collections.CollectionUtils;
  import org.apache.commons.lang.StringUtils;
  import org.apache.commons.validator.routines.DateValidator;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.Date;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  import java.util.TimeZone;
  
  import static org.apache.atlas.model.typedef.AtlasBaseTypeDef.ATLAS_TYPE_DATE;
  import static org.apache.atlas.model.typedef.AtlasBaseTypeDef.ATLAS_TYPE_STRING;
  import static org.apache.atlas.type.Constants.CLASSIFICATION_ENTITY_STATUS_PROPERTY_KEY;
  import static org.apache.atlas.type.Constants.CREATED_BY_KEY;
  import static org.apache.atlas.type.Constants.MODIFICATION_TIMESTAMP_PROPERTY_KEY;
  import static org.apache.atlas.type.Constants.MODIFIED_BY_KEY;
  import static org.apache.atlas.type.Constants.TIMESTAMP_PROPERTY_KEY;
  import static org.apache.atlas.type.Constants.TYPE_NAME_PROPERTY_KEY;
  
  /**
   * class that implements behaviour of a classification-type.
   */
  public class AtlasClassificationType extends AtlasStructType {
      private static final Logger LOG = LoggerFactory.getLogger(AtlasClassificationType.class);
  
      private static final String CLASSIFICATION_ROOT_NAME = "__CLASSIFICATION_ROOT";
      private static final String DEFAULT_GMT_TIMEZONE     = "GMT";
  
      public static final  AtlasClassificationType CLASSIFICATION_ROOT = initRootClassificationType();
  
      private final AtlasClassificationDef classificationDef;
      private final String                 typeQryStr;
  
      private List<AtlasClassificationType> superTypes               = Collections.emptyList();
      private Set<String>                   allSuperTypes            = Collections.emptySet();
      private Set<String>                   subTypes                 = Collections.emptySet();
      private Set<String>                   allSubTypes              = Collections.emptySet();
      private Set<String>                   typeAndAllSubTypes       = Collections.emptySet();
      private Set<String>                   typeAndAllSuperTypes     = Collections.emptySet();
      private String                        typeAndAllSubTypesQryStr = "";
  
      // we need to store the entityTypes specified in our supertypes. i.e. our parent classificationDefs may specify more entityTypes
      // that we also need to allow
      private Set<String> entityTypes = Collections.emptySet();
  
      /**
       * Note this constructor does NOT run resolveReferences - so some fields are not setup.
       * @param classificationDef
       */
      public AtlasClassificationType(AtlasClassificationDef classificationDef) {
          super(classificationDef);
  
          this.classificationDef = classificationDef;
          this.typeQryStr        = AtlasAttribute.escapeIndexQueryValue(Collections.singleton(getTypeName()), true);
      }
  
      /**
       * ClassificationType needs to be constructed with a type registry so that is can resolve references
       * at constructor time. This is only used by junits.
       *
       * @param classificationDef
       * @param typeRegistry
       * @throws AtlasBaseException
       */
      public AtlasClassificationType(AtlasClassificationDef classificationDef, AtlasTypeRegistry typeRegistry) throws AtlasBaseException {
          super(classificationDef);
  
          this.classificationDef = classificationDef;
          this.typeQryStr        = AtlasAttribute.escapeIndexQueryValue(Collections.singleton(getTypeName()), true);
  
          resolveReferences(typeRegistry);
      }
  
      public static AtlasClassificationType getClassificationRoot() {
          return CLASSIFICATION_ROOT;
      }
  
      public static boolean isValidTimeZone(final String timeZone) {
          if (timeZone.equals(DEFAULT_GMT_TIMEZONE)) {
              return true;
          } else {
              // if custom time zone is invalid,
              // time zone id returned is always "GMT" by default
              String id = TimeZone.getTimeZone(timeZone).getID();
              return !id.equals(DEFAULT_GMT_TIMEZONE);
          }
      }
  
      public AtlasClassificationDef getClassificationDef() {
          return classificationDef;
      }
  
      public Set<String> getSuperTypes() {
          return classificationDef.getSuperTypes();
      }
  
      public Set<String> getAllSuperTypes() {
          return allSuperTypes;
      }
  
      public Set<String> getSubTypes() {
          return subTypes;
      }
  
      public Set<String> getAllSubTypes() {
          return allSubTypes;
      }
  
      public Set<String> getTypeAndAllSubTypes() {
          return typeAndAllSubTypes;
      }
  
      public Set<String> getTypeAndAllSuperTypes() {
          return typeAndAllSuperTypes;
      }
  
      public String getTypeQryStr() {
          return typeQryStr;
      }
  
      public String getTypeAndAllSubTypesQryStr() {
          if (StringUtils.isEmpty(typeAndAllSubTypesQryStr)) {
              typeAndAllSubTypesQryStr = AtlasAttribute.escapeIndexQueryValue(typeAndAllSubTypes, true);
          }
  
          return typeAndAllSubTypesQryStr;
      }
  
      public boolean isSuperTypeOf(AtlasClassificationType classificationType) {
          return classificationType != null && allSubTypes.contains(classificationType.getTypeName());
      }
  
      public boolean isSuperTypeOf(String classificationName) {
          return StringUtils.isNotEmpty(classificationName) && allSubTypes.contains(classificationName);
      }
  
      public boolean isSubTypeOf(AtlasClassificationType classificationType) {
          return classificationType != null && allSuperTypes.contains(classificationType.getTypeName());
      }
  
      public boolean isSubTypeOf(String classificationName) {
          return StringUtils.isNotEmpty(classificationName) && allSuperTypes.contains(classificationName);
      }
  
      public boolean hasAttribute(String attrName) {
          return allAttributes.containsKey(attrName);
      }
  
      /**
       * List of all the entity type names that are valid for this classification type.
       *
       * An empty list means there are no restrictions on which entities can be classified by these classifications.
       * @return
       */
      public Set<String> getEntityTypes() {
          return entityTypes;
      }
  
      @Override
      public AtlasClassification createDefaultValue() {
          AtlasClassification ret = new AtlasClassification(classificationDef.getName());
  
          populateDefaultValues(ret);
  
          return ret;
      }
  
      @Override
      public boolean isValidValue(Object obj) {
          if (obj != null) {
              for (AtlasClassificationType superType : superTypes) {
                  if (!superType.isValidValue(obj)) {
                      return false;
                  }
              }
  
              if (!validateTimeBoundaries(obj, null)) {
                  return false;
              }
  
              return super.isValidValue(obj);
          }
  
          return true;
      }
  
      @Override
      public boolean areEqualValues(Object val1, Object val2, Map<String, String> guidAssignments) {
          for (AtlasClassificationType superType : superTypes) {
              if (!superType.areEqualValues(val1, val2, guidAssignments)) {
                  return false;
              }
          }
  
          return super.areEqualValues(val1, val2, guidAssignments);
      }
  
      @Override
      public Object getNormalizedValue(Object obj) {
          Object ret = null;
  
          if (obj != null) {
              if (isValidValue(obj)) {
                  if (obj instanceof AtlasClassification) {
                      normalizeAttributeValues((AtlasClassification) obj);
                      ret = obj;
                  } else if (obj instanceof Map) {
                      normalizeAttributeValues((Map) obj);
                      ret = obj;
                  }
              }
          }
  
          return ret;
      }
  
      @Override
      public boolean validateValue(Object obj, String objName, List<String> messages) {
          boolean ret = true;
  
          if (obj != null) {
              for (AtlasClassificationType superType : superTypes) {
                  ret = superType.validateValue(obj, objName, messages) && ret;
              }
  
              ret = validateTimeBoundaries(obj, messages) && ret;
  
              ret = super.validateValue(obj, objName, messages) && ret;
          }
  
          return ret;
      }
  
      @Override
      public boolean isValidValueForUpdate(Object obj) {
          if (obj != null) {
              for (AtlasClassificationType superType : superTypes) {
                  if (!superType.isValidValueForUpdate(obj)) {
                      return false;
                  }
              }
  
              if (!validateTimeBoundaries(obj, null)) {
                  return false;
              }
  
              return super.isValidValueForUpdate(obj);
          }
  
          return true;
      }
  
      @Override
      public Object getNormalizedValueForUpdate(Object obj) {
          Object ret = null;
  
          if (obj != null) {
              if (isValidValueForUpdate(obj)) {
                  if (obj instanceof AtlasClassification) {
                      normalizeAttributeValuesForUpdate((AtlasClassification) obj);
                      ret = obj;
                  } else if (obj instanceof Map) {
                      normalizeAttributeValuesForUpdate((Map) obj);
                      ret = obj;
                  }
              }
          }
  
          return ret;
      }
  
      @Override
      public boolean validateValueForUpdate(Object obj, String objName, List<String> messages) {
          boolean ret = true;
  
          if (obj != null) {
              for (AtlasClassificationType superType : superTypes) {
                  ret = superType.validateValueForUpdate(obj, objName, messages) && ret;
              }
  
              ret = validateTimeBoundaries(obj, messages) && ret;
  
              ret = super.validateValueForUpdate(obj, objName, messages) && ret;
          }
  
          return ret;
      }
  
      @Override
      void resolveReferences(AtlasTypeRegistry typeRegistry) throws AtlasBaseException {
          super.resolveReferences(typeRegistry);
  
          List<AtlasClassificationType> s    = new ArrayList<>();
          Set<String>                   allS = new HashSet<>();
          Map<String, AtlasAttribute>   allA = new HashMap<>();
  
          getTypeHierarchyInfo(typeRegistry, allS, allA);
  
          for (String superTypeName : classificationDef.getSuperTypes()) {
              AtlasType superType = typeRegistry.getType(superTypeName);
  
              if (superType instanceof AtlasClassificationType) {
                  s.add((AtlasClassificationType) superType);
              } else {
                  throw new AtlasBaseException(AtlasErrorCode.INCOMPATIBLE_SUPERTYPE, superTypeName,
                          classificationDef.getName());
              }
          }
  
          this.superTypes         = Collections.unmodifiableList(s);
          this.allSuperTypes      = Collections.unmodifiableSet(allS);
          this.allAttributes      = Collections.unmodifiableMap(allA);
          this.uniqAttributes     = getUniqueAttributes(this.allAttributes);
          this.subTypes           = new HashSet<>(); // this will be populated in resolveReferencesPhase2()
          this.allSubTypes        = new HashSet<>(); // this will be populated in resolveReferencesPhase2()
          this.typeAndAllSubTypes = new HashSet<>(); // this will be populated in resolveReferencesPhase2()
          this.entityTypes        = new HashSet<>(); // this will be populated in resolveReferencesPhase3()
  
          this.typeAndAllSubTypes.add(this.getTypeName());
  
          this.typeAndAllSuperTypes = new HashSet<>(this.allSuperTypes);
          this.typeAndAllSuperTypes.add(this.getTypeName());
          this.typeAndAllSuperTypes = Collections.unmodifiableSet(this.typeAndAllSuperTypes);
      }
  
      @Override
      void resolveReferencesPhase2(AtlasTypeRegistry typeRegistry) throws AtlasBaseException {
          super.resolveReferencesPhase2(typeRegistry);
  
          for (AtlasClassificationType superType : superTypes) {
              superType.addSubType(this);
          }
  
          for (String superTypeName : allSuperTypes) {
              AtlasClassificationType superType = typeRegistry.getClassificationTypeByName(superTypeName);
              superType.addToAllSubTypes(this);
          }
      }
  
      @Override
      public AtlasAttribute getSystemAttribute(String attributeName) {
          return AtlasClassificationType.CLASSIFICATION_ROOT.allAttributes.get(attributeName);
      }
  
      @Override
      public void normalizeAttributeValues(Map<String, Object> obj) {
          if (obj != null) {
              for (AtlasClassificationType superType : superTypes) {
                  superType.normalizeAttributeValues(obj);
              }
  
              super.normalizeAttributeValues(obj);
          }
      }
  
      public void normalizeAttributeValuesForUpdate(Map<String, Object> obj) {
          if (obj != null) {
              for (AtlasClassificationType superType : superTypes) {
                  superType.normalizeAttributeValuesForUpdate(obj);
              }
  
              super.normalizeAttributeValuesForUpdate(obj);
          }
      }
  
      public void normalizeAttributeValues(AtlasClassification classification) {
          if (classification != null) {
              for (AtlasClassificationType superType : superTypes) {
                  superType.normalizeAttributeValues(classification);
              }
  
              super.normalizeAttributeValues(classification);
          }
      }
  
      public void normalizeAttributeValuesForUpdate(AtlasClassification classification) {
          if (classification != null) {
              for (AtlasClassificationType superType : superTypes) {
                  superType.normalizeAttributeValuesForUpdate(classification);
              }
  
              super.normalizeAttributeValuesForUpdate(classification);
          }
      }
  
      public void populateDefaultValues(AtlasClassification classification) {
          if (classification != null) {
              for (AtlasClassificationType superType : superTypes) {
                  superType.populateDefaultValues(classification);
              }
  
              super.populateDefaultValues(classification);
          }
      }
  
      /**
       * Check whether the supplied entityType can be applied to this classification.
       *
       * @param entityType
       * @return whether can apply
       */
      public boolean canApplyToEntityType(AtlasEntityType entityType) {
          return CollectionUtils.isEmpty(this.entityTypes) || this.entityTypes.contains(entityType.getTypeName());
      }
  
      /**
       * This method processes the entityTypes to ensure they are valid, using the following principles:
       * - entityTypes are supplied on the classificationDef to restrict the types of entities that this classification can be applied to
       * - Any subtypes of the specified entity type can also have this classification applied
       * - Any subtypes of the classificationDef inherit the parents entityTypes restrictions
       * - Any subtypes of the classificationDef can further restrict the parents entityTypes restrictions
       * - An empty entityTypes list when there are no parent restrictions means there are no restrictions
       * - An empty entityTypes list when there are parent restrictions means that the subtype picks up the parents restrictions
       *
       * This method validates that these priniciples are adhered to.
       *
       * Note that if duplicate Strings in the entityTypes are specified on an add / update, the duplicates are ignored - as Java Sets cannot have duplicates.
       * Note if an entityType is supplied in the list that is a subtype of one of the other supplied entityTypes, we are not policing this case as invalid.
       *
       * @param typeRegistry
       * @throws AtlasBaseException
       */
      @Override
      void resolveReferencesPhase3(AtlasTypeRegistry typeRegistry) throws AtlasBaseException {
          subTypes                 = Collections.unmodifiableSet(subTypes);
          allSubTypes              = Collections.unmodifiableSet(allSubTypes);
          typeAndAllSubTypes       = Collections.unmodifiableSet(typeAndAllSubTypes);
          typeAndAllSubTypesQryStr = ""; // will be computed on next access
  
          /*
            Add any entityTypes defined in our parents as restrictions.
           */
          Set<String> superTypeEntityTypes = null;
  
          final Set<String> classificationDefEntityTypes = classificationDef.getEntityTypes();
  
          // Here we find the intersection of the entityTypes specified in all our supertypes; in this way we will honour our parents restrictions.
          // This following logic assumes typeAndAllSubTypes is populated so needs to be run after resolveReferencesPhase2().
  
          for (String superType : this.allSuperTypes) {
              AtlasClassificationDef superTypeDef    = typeRegistry.getClassificationDefByName(superType);
              Set<String>            entityTypeNames = superTypeDef.getEntityTypes();
  
              if (CollectionUtils.isEmpty(entityTypeNames)) { // no restrictions specified
                  continue;
              }
  
              // classification is applicable for specified entityTypes and their sub-entityTypes
              Set<String> typesAndSubEntityTypes = AtlasEntityType.getEntityTypesAndAllSubTypes(entityTypeNames, typeRegistry);
  
              if (superTypeEntityTypes == null) {
                  superTypeEntityTypes = new HashSet<>(typesAndSubEntityTypes);
              } else {
                  // retain only the intersections.
                  superTypeEntityTypes.retainAll(typesAndSubEntityTypes);
              }
              if (superTypeEntityTypes.isEmpty()) {
                  // if we have no intersections then we are disjoint - so no need to check other supertypes
                  break;
              }
          }
  
          if (superTypeEntityTypes == null) {  // no supertype restrictions; use current classification restrictions
              this.entityTypes = AtlasEntityType.getEntityTypesAndAllSubTypes(classificationDefEntityTypes, typeRegistry);
          } else {                             // restrictions are specified in super-types
              if (CollectionUtils.isEmpty(superTypeEntityTypes)) {
                  /*
                   Restrictions in superTypes are disjoint! This means that the child cannot exist as it cannot be a restriction of it's parents.
  
                   For example:
                    parent1 specifies entityTypes ["EntityA"]
                    parent2 specifies entityTypes ["EntityB"]
  
                    In order to be a valid child of Parent1 the child could only be applied to EntityAs.
                    In order to be a valid child of Parent2 the child could only be applied to EntityBs.
  
                    Reject the creation of the classificationDef - as it would compromise Atlas's integrity.
                   */
                  throw new AtlasBaseException(AtlasErrorCode.CLASSIFICATIONDEF_PARENTS_ENTITYTYPES_DISJOINT, this.classificationDef.getName());
              }
  
              if (CollectionUtils.isEmpty(classificationDefEntityTypes)) { // no restriction specified; use the restrictions from super-types
                  this.entityTypes = superTypeEntityTypes;
              } else {
                  this.entityTypes = AtlasEntityType.getEntityTypesAndAllSubTypes(classificationDefEntityTypes, typeRegistry);
                  // Compatible parents and entityTypes, now check whether the specified entityTypes are the same as the effective entityTypes due to our parents or a subset.
                  // Only allowed to restrict our parents.
                  if (!superTypeEntityTypes.containsAll(this.entityTypes)) {
                      throw new AtlasBaseException(AtlasErrorCode.CLASSIFICATIONDEF_ENTITYTYPES_NOT_PARENTS_SUBSET, classificationDef.getName(), classificationDefEntityTypes.toString());
                  }
              }
          }
  
          classificationDef.setSubTypes(subTypes);
      }
  
      /**
       * Check whether the supplied entityType can be applied to this classification.
       *
       * We can apply this classification to the supplied entityType if
       * - we have no restrictions (entityTypes empty including null)
       * or
       * - the entityType is in our list of restricted entityTypes (which includes our parent classification restrictions)
       *
       * @param subType
       */
  
      private void addSubType(AtlasClassificationType subType) {
          subTypes.add(subType.getTypeName());
      }
  
      private void addToAllSubTypes(AtlasClassificationType subType) {
          allSubTypes.add(subType.getTypeName());
          typeAndAllSubTypes.add(subType.getTypeName());
      }
  
      private static AtlasClassificationType initRootClassificationType() {
          List<AtlasAttributeDef> attributeDefs = new ArrayList<>();
  
          attributeDefs.add(new AtlasAttributeDef(TYPE_NAME_PROPERTY_KEY, AtlasBaseTypeDef.ATLAS_TYPE_STRING, false, true));
          attributeDefs.add(new AtlasAttributeDef(TIMESTAMP_PROPERTY_KEY, ATLAS_TYPE_DATE, false, true));
          attributeDefs.add(new AtlasAttributeDef(MODIFICATION_TIMESTAMP_PROPERTY_KEY, ATLAS_TYPE_DATE, false, true));
          attributeDefs.add(new AtlasAttributeDef(MODIFIED_BY_KEY, ATLAS_TYPE_STRING, false, true));
          attributeDefs.add(new AtlasAttributeDef(CREATED_BY_KEY, ATLAS_TYPE_STRING, false, true));
          attributeDefs.add(new AtlasAttributeDef(CLASSIFICATION_ENTITY_STATUS_PROPERTY_KEY, ATLAS_TYPE_STRING, false, true));
  
          AtlasClassificationDef classificationDef = new AtlasClassificationDef(CLASSIFICATION_ROOT_NAME, "Root classification for system attributes", "1.0", attributeDefs);
  
          return new AtlasClassificationType(classificationDef);
      }
  
      private void getTypeHierarchyInfo(AtlasTypeRegistry typeRegistry,
              Set<String> allSuperTypeNames,
              Map<String, AtlasAttribute> allAttributes) throws AtlasBaseException {
          List<String>        visitedTypes                     = new ArrayList<>();
          Map<String, String> attributeToClassificationNameMap = new HashMap<>();
  
          collectTypeHierarchyInfo(typeRegistry, allSuperTypeNames, allAttributes, attributeToClassificationNameMap, visitedTypes);
      }
  
      /*
       * This method should not assume that resolveReferences() has been called on all superTypes.
       * this.classificationDef is the only safe member to reference here
       */
      private void collectTypeHierarchyInfo(AtlasTypeRegistry typeRegistry,
              Set<String> allSuperTypeNames,
              Map<String, AtlasAttribute> allAttributes,
              Map<String, String> attributeToClassificationNameMap,
              List<String> visitedTypes) throws AtlasBaseException {
          if (visitedTypes.contains(classificationDef.getName())) {
              throw new AtlasBaseException(AtlasErrorCode.CIRCULAR_REFERENCE, classificationDef.getName(),
                      visitedTypes.toString());
          }
  
          if (CollectionUtils.isNotEmpty(classificationDef.getSuperTypes())) {
              visitedTypes.add(classificationDef.getName());
              for (String superTypeName : classificationDef.getSuperTypes()) {
                  AtlasClassificationType superType = typeRegistry.getClassificationTypeByName(superTypeName);
  
                  if (superType != null) {
                      superType.collectTypeHierarchyInfo(typeRegistry, allSuperTypeNames, allAttributes, attributeToClassificationNameMap, visitedTypes);
                  }
              }
              visitedTypes.remove(classificationDef.getName());
  
              allSuperTypeNames.addAll(classificationDef.getSuperTypes());
          }
  
          if (CollectionUtils.isNotEmpty(classificationDef.getAttributeDefs())) {
              for (AtlasAttributeDef attributeDef : classificationDef.getAttributeDefs()) {
                  AtlasType type          = typeRegistry.getType(attributeDef.getTypeName());
                  String    attributeName = attributeDef.getName();
  
                  if (attributeToClassificationNameMap.containsKey(attributeName) && !attributeToClassificationNameMap.get(attributeName).equals(classificationDef.getName())) {
                      if (skipCheckForParentChildAttributeName) {
                          LOG.warn(AtlasErrorCode.ATTRIBUTE_NAME_ALREADY_EXISTS_IN_ANOTHER_PARENT_TYPE.getFormattedErrorMessage(classificationDef.getName(), attributeName, attributeToClassificationNameMap.get(attributeName)));
                      } else {
                          throw new AtlasBaseException(AtlasErrorCode.ATTRIBUTE_NAME_ALREADY_EXISTS_IN_ANOTHER_PARENT_TYPE, classificationDef.getName(), attributeName, attributeToClassificationNameMap.get(attributeName));
                      }
                  }
  
                  allAttributes.put(attributeName, new AtlasAttribute(this, attributeDef, type));
                  attributeToClassificationNameMap.put(attributeName, classificationDef.getName());
              }
          }
      }
  
      private boolean validateTimeBoundaries(Object classificationObj, List<String> messages) {
          boolean             ret            = true;
          AtlasClassification classification = null;
  
          if (classificationObj instanceof AtlasClassification) {
              classification = (AtlasClassification) classificationObj;
          } else if (classificationObj instanceof Map) {
              classification = new AtlasClassification((Map) classificationObj);
          }
  
          if (classification != null && classification.getValidityPeriods() != null) {
              for (TimeBoundary timeBoundary : classification.getValidityPeriods()) {
                  if (timeBoundary != null) {
                      ret = validateTimeBoundry(timeBoundary, messages) && ret;
                  }
              }
          }
  
          return ret;
      }
  
      private boolean validateTimeBoundry(TimeBoundary timeBoundary, List<String> messages) {
          boolean        ret           = true;
          DateValidator  dateValidator = DateValidator.getInstance();
          Date           startDate     = null;
          Date           endDate       = null;
          final TimeZone timezone;
  
          if (StringUtils.isNotEmpty(timeBoundary.getTimeZone())) {
              if (!isValidTimeZone(timeBoundary.getTimeZone())) {
                  addValidationMessageIfNotPresent(new AtlasBaseException(AtlasErrorCode.INVALID_TIMEBOUNDRY_TIMEZONE, timeBoundary.getTimeZone()), messages);
  
                  ret = false;
              }
  
              timezone = TimeZone.getTimeZone(timeBoundary.getTimeZone());
          } else {
              timezone = TimeZone.getDefault();
          }
  
          if (StringUtils.isNotEmpty(timeBoundary.getStartTime())) {
              startDate = dateValidator.validate(timeBoundary.getStartTime(), TimeBoundary.TIME_FORMAT, timezone);
  
              if (startDate == null) {
                  addValidationMessageIfNotPresent(new AtlasBaseException(AtlasErrorCode.INVALID_TIMEBOUNDRY_START_TIME, timeBoundary.getStartTime()), messages);
  
                  ret = false;
              }
          }
  
          if (StringUtils.isNotEmpty(timeBoundary.getEndTime())) {
              endDate = dateValidator.validate(timeBoundary.getEndTime(), TimeBoundary.TIME_FORMAT, timezone);
  
              if (endDate == null) {
                  addValidationMessageIfNotPresent(new AtlasBaseException(AtlasErrorCode.INVALID_TIMEBOUNDRY_END_TIME, timeBoundary.getEndTime()), messages);
  
                  ret = false;
              }
          }
  
          if (startDate != null && endDate != null) {
              if (endDate.before(startDate)) {
                  addValidationMessageIfNotPresent(new AtlasBaseException(AtlasErrorCode.INVALID_TIMEBOUNDRY_DATERANGE, timeBoundary.getStartTime(), timeBoundary.getEndTime()), messages);
  
                  ret = false;
              }
          }
  
          return ret;
      }
  
      private void addValidationMessageIfNotPresent(AtlasBaseException excp, List<String> messages) {
          String msg = excp.getMessage();
  
          if (messages != null && !messages.contains(msg)) {
              messages.add(msg);
          }
      }
  }
  