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
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package org.apache.ambari.server.controller.internal;
  
  import java.text.MessageFormat;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.EnumSet;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Map.Entry;
  import java.util.Set;
  
  import org.apache.ambari.server.AmbariException;
  import org.apache.ambari.server.DuplicateResourceException;
  import org.apache.ambari.server.ObjectNotFoundException;
  import org.apache.ambari.server.api.resources.OperatingSystemResourceDefinition;
  import org.apache.ambari.server.api.resources.RepositoryResourceDefinition;
  import org.apache.ambari.server.api.services.AmbariMetaInfo;
  import org.apache.ambari.server.controller.spi.NoSuchParentResourceException;
  import org.apache.ambari.server.controller.spi.NoSuchResourceException;
  import org.apache.ambari.server.controller.spi.Predicate;
  import org.apache.ambari.server.controller.spi.Request;
  import org.apache.ambari.server.controller.spi.RequestStatus;
  import org.apache.ambari.server.controller.spi.Resource;
  import org.apache.ambari.server.controller.spi.Resource.Type;
  import org.apache.ambari.server.controller.spi.ResourceAlreadyExistsException;
  import org.apache.ambari.server.controller.spi.SystemException;
  import org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
  import org.apache.ambari.server.controller.utilities.PropertyHelper;
  import org.apache.ambari.server.orm.dao.HostVersionDAO;
  import org.apache.ambari.server.orm.dao.RepositoryVersionDAO;
  import org.apache.ambari.server.orm.dao.StackDAO;
  import org.apache.ambari.server.orm.entities.HostVersionEntity;
  import org.apache.ambari.server.orm.entities.RepoDefinitionEntity;
  import org.apache.ambari.server.orm.entities.RepoOsEntity;
  import org.apache.ambari.server.orm.entities.RepositoryVersionEntity;
  import org.apache.ambari.server.orm.entities.StackEntity;
  import org.apache.ambari.server.security.authorization.AuthorizationException;
  import org.apache.ambari.server.security.authorization.AuthorizationHelper;
  import org.apache.ambari.server.security.authorization.ResourceType;
  import org.apache.ambari.server.security.authorization.RoleAuthorization;
  import org.apache.ambari.server.stack.upgrade.RepositoryVersionHelper;
  import org.apache.ambari.server.state.OperatingSystemInfo;
  import org.apache.ambari.server.state.RepositoryVersionState;
  import org.apache.ambari.server.state.ServiceInfo;
  import org.apache.ambari.server.state.StackId;
  import org.apache.ambari.server.state.StackInfo;
  import org.apache.ambari.server.state.repository.ManifestServiceInfo;
  import org.apache.ambari.server.state.repository.VersionDefinitionXml;
  import org.apache.ambari.server.utils.URLCredentialsHider;
  import org.apache.commons.collections4.CollectionUtils;
  import org.apache.commons.lang.ObjectUtils;
  import org.apache.commons.lang.StringUtils;
  
  import com.google.common.collect.ImmutableMap;
  import com.google.common.collect.ImmutableSet;
  import com.google.common.collect.Sets;
  import com.google.gson.Gson;
  import com.google.inject.Inject;
  import com.google.inject.persist.Transactional;
  
  /**
   * Resource provider for repository versions resources.
   */
  public class RepositoryVersionResourceProvider extends AbstractAuthorizedResourceProvider {
  
    // ----- Property ID constants ---------------------------------------------
    public static final String REPOSITORY_VERSION                                = "RepositoryVersions";
    public static final String REPOSITORY_VERSION_ID_PROPERTY_ID                 = PropertyHelper.getPropertyId("RepositoryVersions", "id");
    public static final String REPOSITORY_VERSION_STACK_NAME_PROPERTY_ID         = PropertyHelper.getPropertyId("RepositoryVersions", "stack_name");
    public static final String REPOSITORY_VERSION_STACK_VERSION_PROPERTY_ID      = PropertyHelper.getPropertyId("RepositoryVersions", "stack_version");
    public static final String REPOSITORY_VERSION_REPOSITORY_VERSION_PROPERTY_ID = PropertyHelper.getPropertyId("RepositoryVersions", "repository_version");
    public static final String REPOSITORY_VERSION_DISPLAY_NAME_PROPERTY_ID       = PropertyHelper.getPropertyId("RepositoryVersions", "display_name");
    public static final String REPOSITORY_VERSION_HIDDEN_PROPERTY_ID             = PropertyHelper.getPropertyId("RepositoryVersions", "hidden");
    public static final String REPOSITORY_VERSION_RESOLVED_PROPERTY_ID           = PropertyHelper.getPropertyId("RepositoryVersions", "resolved");
    public static final String SUBRESOURCE_OPERATING_SYSTEMS_PROPERTY_ID         = new OperatingSystemResourceDefinition().getPluralName();
    public static final String SUBRESOURCE_REPOSITORIES_PROPERTY_ID              = new RepositoryResourceDefinition().getPluralName();
  
    public static final String REPOSITORY_VERSION_TYPE_PROPERTY_ID               = "RepositoryVersions/type";
    public static final String REPOSITORY_VERSION_RELEASE_VERSION                = "RepositoryVersions/release/version";
    public static final String REPOSITORY_VERSION_RELEASE_BUILD                  = "RepositoryVersions/release/build";
    public static final String REPOSITORY_VERSION_RELEASE_NOTES                  = "RepositoryVersions/release/notes";
    public static final String REPOSITORY_VERSION_RELEASE_COMPATIBLE_WITH        = "RepositoryVersions/release/compatible_with";
    public static final String REPOSITORY_VERSION_AVAILABLE_SERVICES             = "RepositoryVersions/services";
    public static final String REPOSITORY_VERSION_STACK_SERVICES                 = "RepositoryVersions/stack_services";
  
    public static final String REPOSITORY_VERSION_PARENT_ID                      = "RepositoryVersions/parent_id";
    public static final String REPOSITORY_VERSION_HAS_CHILDREN                   = "RepositoryVersions/has_children";
  
    @SuppressWarnings("serial")
    private static final Set<String> pkPropertyIds = ImmutableSet.of(REPOSITORY_VERSION_ID_PROPERTY_ID);
  
    @SuppressWarnings("serial")
    public static final Set<String> propertyIds = ImmutableSet.of(
        REPOSITORY_VERSION_ID_PROPERTY_ID,
        REPOSITORY_VERSION_REPOSITORY_VERSION_PROPERTY_ID,
        REPOSITORY_VERSION_DISPLAY_NAME_PROPERTY_ID,
        REPOSITORY_VERSION_HIDDEN_PROPERTY_ID,
        REPOSITORY_VERSION_STACK_NAME_PROPERTY_ID,
        REPOSITORY_VERSION_STACK_VERSION_PROPERTY_ID,
        SUBRESOURCE_OPERATING_SYSTEMS_PROPERTY_ID,
        REPOSITORY_VERSION_TYPE_PROPERTY_ID,
        REPOSITORY_VERSION_RELEASE_BUILD,
        REPOSITORY_VERSION_RELEASE_COMPATIBLE_WITH,
        REPOSITORY_VERSION_RELEASE_NOTES,
        REPOSITORY_VERSION_RELEASE_VERSION,
        REPOSITORY_VERSION_PARENT_ID,
        REPOSITORY_VERSION_HAS_CHILDREN,
        REPOSITORY_VERSION_AVAILABLE_SERVICES,
        REPOSITORY_VERSION_STACK_SERVICES,
        REPOSITORY_VERSION_RESOLVED_PROPERTY_ID);
  
    @SuppressWarnings("serial")
    public static final Map<Type, String> keyPropertyIds = new ImmutableMap.Builder<Type, String>()
        .put(Type.Stack, REPOSITORY_VERSION_STACK_NAME_PROPERTY_ID)
        .put(Type.StackVersion, REPOSITORY_VERSION_STACK_VERSION_PROPERTY_ID)
        .put(Type.RepositoryVersion, REPOSITORY_VERSION_ID_PROPERTY_ID)
        .build();
  
    @Inject
    private Gson gson;
  
    @Inject
    private RepositoryVersionDAO repositoryVersionDAO;
  
    @Inject
    private AmbariMetaInfo ambariMetaInfo;
  
    @Inject
    private RepositoryVersionHelper repositoryVersionHelper;
  
    /**
     * Data access object used for lookup up stacks.
     */
    @Inject
    private StackDAO stackDAO;
  
    @Inject
    HostVersionDAO hostVersionDAO;
  
    /**
     * Create a new resource provider.
     *
     */
    public RepositoryVersionResourceProvider() {
      super(Resource.Type.RepositoryVersion, propertyIds, keyPropertyIds);
  
      setRequiredCreateAuthorizations(EnumSet.of(RoleAuthorization.AMBARI_MANAGE_STACK_VERSIONS));
      setRequiredDeleteAuthorizations(EnumSet.of(RoleAuthorization.AMBARI_MANAGE_STACK_VERSIONS));
      setRequiredUpdateAuthorizations(EnumSet.of(RoleAuthorization.AMBARI_MANAGE_STACK_VERSIONS, RoleAuthorization.AMBARI_EDIT_STACK_REPOS));
  
      setRequiredGetAuthorizations(EnumSet.of(
          RoleAuthorization.AMBARI_MANAGE_STACK_VERSIONS,
          RoleAuthorization.AMBARI_EDIT_STACK_REPOS,
          RoleAuthorization.CLUSTER_VIEW_STACK_DETAILS,
          RoleAuthorization.CLUSTER_UPGRADE_DOWNGRADE_STACK));
    }
  
    @Override
    protected RequestStatus createResourcesAuthorized(final Request request)
        throws SystemException,
        UnsupportedPropertyException,
        ResourceAlreadyExistsException,
        NoSuchParentResourceException {
  
      for (final Map<String, Object> properties : request.getProperties()) {
        createResources(new Command<Void>() {
  
          @Override
          public Void invoke() throws AmbariException {
            final String[] requiredProperties = {
              REPOSITORY_VERSION_DISPLAY_NAME_PROPERTY_ID,
              SUBRESOURCE_OPERATING_SYSTEMS_PROPERTY_ID,
              REPOSITORY_VERSION_STACK_NAME_PROPERTY_ID,
              REPOSITORY_VERSION_STACK_VERSION_PROPERTY_ID,
              REPOSITORY_VERSION_REPOSITORY_VERSION_PROPERTY_ID
            };
  
            for (String propertyName : requiredProperties) {
              if (properties.get(propertyName) == null) {
                throw new AmbariException("Property " + propertyName + " should be provided");
              }
            }
  
            RepositoryVersionEntity entity = toRepositoryVersionEntity(properties);
  
            if (repositoryVersionDAO.findByDisplayName(entity.getDisplayName()) != null) {
              throw new DuplicateResourceException("Repository version with name " + entity.getDisplayName() + " already exists");
            }
            if (repositoryVersionDAO.findByStackAndVersion(entity.getStack(), entity.getVersion()) != null) {
              throw new DuplicateResourceException("Repository version for stack " + entity.getStack() + " and version " + entity.getVersion() + " already exists");
            }
  
            validateRepositoryVersion(repositoryVersionDAO, ambariMetaInfo, entity);
  
            repositoryVersionDAO.create(entity);
            notifyCreate(Resource.Type.RepositoryVersion, request);
            return null;
          }
        });
      }
  
      return getRequestStatus(null);
    }
  
    @Override
    protected Set<Resource> getResourcesAuthorized(Request request, Predicate predicate)
        throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
      final Set<Resource> resources = new HashSet<>();
      final Set<String> requestedIds = getRequestPropertyIds(request, predicate);
      final Set<Map<String, Object>> propertyMaps = getPropertyMaps(predicate);
  
  
      List<RepositoryVersionEntity> requestedEntities = new ArrayList<>();
      for (Map<String, Object> propertyMap: propertyMaps) {
        final StackId stackId = getStackInformationFromUrl(propertyMap);
  
        if (stackId != null && propertyMaps.size() == 1 && propertyMap.get(REPOSITORY_VERSION_ID_PROPERTY_ID) == null) {
          requestedEntities.addAll(repositoryVersionDAO.findByStack(stackId));
        } else {
          final Long id;
          try {
            id = Long.parseLong(propertyMap.get(REPOSITORY_VERSION_ID_PROPERTY_ID).toString());
          } catch (Exception ex) {
            throw new SystemException("Repository version should have numerical id");
          }
          final RepositoryVersionEntity entity = repositoryVersionDAO.findByPK(id);
  
          if (entity == null) {
            throw new NoSuchResourceException("There is no repository version with id " + id);
          } else {
            requestedEntities.add(entity);
          }
        }
      }
  
      for (RepositoryVersionEntity entity: requestedEntities) {
        final Resource resource = new ResourceImpl(Resource.Type.RepositoryVersion);
  
        setResourceProperty(resource, REPOSITORY_VERSION_ID_PROPERTY_ID, entity.getId(), requestedIds);
        setResourceProperty(resource, REPOSITORY_VERSION_STACK_NAME_PROPERTY_ID, entity.getStackName(), requestedIds);
        setResourceProperty(resource, REPOSITORY_VERSION_STACK_VERSION_PROPERTY_ID, entity.getStackVersion(), requestedIds);
        setResourceProperty(resource, REPOSITORY_VERSION_DISPLAY_NAME_PROPERTY_ID, entity.getDisplayName(), requestedIds);
        setResourceProperty(resource, REPOSITORY_VERSION_HIDDEN_PROPERTY_ID, entity.isHidden(), requestedIds);
        setResourceProperty(resource, REPOSITORY_VERSION_REPOSITORY_VERSION_PROPERTY_ID, entity.getVersion(), requestedIds);
        setResourceProperty(resource, REPOSITORY_VERSION_TYPE_PROPERTY_ID, entity.getType(), requestedIds);
        setResourceProperty(resource, REPOSITORY_VERSION_RESOLVED_PROPERTY_ID, entity.isResolved(), requestedIds);
        setResourceProperty(resource, REPOSITORY_VERSION_PARENT_ID, entity.getParentId(), requestedIds);
  
        List<RepositoryVersionEntity> children = entity.getChildren();
        setResourceProperty(resource, REPOSITORY_VERSION_HAS_CHILDREN,
            null != children && !children.isEmpty(), requestedIds);
  
        final VersionDefinitionXml xml;
        try {
          xml = entity.getRepositoryXml();
        } catch (Exception e) {
          throw new SystemException(String.format("Could not load xml for Repository %s", entity.getId()), e);
        }
  
        final StackInfo stack;
        try {
          stack = ambariMetaInfo.getStack(entity.getStackName(), entity.getStackVersion());
        } catch (AmbariException e) {
          throw new SystemException(String.format("Could not load stack %s for Repository %s",
              entity.getStackId().toString(), entity.getId()));
        }
  
        final List<ManifestServiceInfo> stackServices;
  
        if (null != xml) {
          setResourceProperty(resource, REPOSITORY_VERSION_RELEASE_VERSION, xml.release.version, requestedIds);
          setResourceProperty(resource, REPOSITORY_VERSION_RELEASE_BUILD, xml.release.build, requestedIds);
          setResourceProperty(resource, REPOSITORY_VERSION_RELEASE_COMPATIBLE_WITH, xml.release.compatibleWith, requestedIds);
          setResourceProperty(resource, REPOSITORY_VERSION_RELEASE_NOTES, xml.release.releaseNotes, requestedIds);
          setResourceProperty(resource, REPOSITORY_VERSION_AVAILABLE_SERVICES, xml.getAvailableServices(stack), requestedIds);
          stackServices = xml.getStackServices(stack);
        } else {
          stackServices = new ArrayList<>();
  
          for (ServiceInfo si : stack.getServices()) {
            stackServices.add(new ManifestServiceInfo(si.getName(), si.getDisplayName(), si.getComment(),
                Collections.singleton(si.getVersion())));
          }
  
        }
  
        setResourceProperty(resource, REPOSITORY_VERSION_STACK_SERVICES, stackServices, requestedIds);
  
        resources.add(resource);
      }
      return resources;
    }
  
    @Override
    @Transactional
    protected RequestStatus updateResourcesAuthorized(Request request, Predicate predicate)
      throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
      final Set<Map<String, Object>> propertyMaps = request.getProperties();
  
      modifyResources(new Command<Void>() {
        @Override
        public Void invoke() throws AmbariException, AuthorizationException {
          for (Map<String, Object> propertyMap : propertyMaps) {
            final Long id;
            try {
              id = Long.parseLong(propertyMap.get(REPOSITORY_VERSION_ID_PROPERTY_ID).toString());
            } catch (Exception ex) {
              throw new AmbariException("Repository version should have numerical id");
            }
  
            final RepositoryVersionEntity entity = repositoryVersionDAO.findByPK(id);
            if (entity == null) {
              throw new ObjectNotFoundException("There is no repository version with id " + id);
            }
  
            if (StringUtils.isNotBlank(ObjectUtils.toString(propertyMap.get(SUBRESOURCE_OPERATING_SYSTEMS_PROPERTY_ID)))) {
              if (!AuthorizationHelper.isAuthorized(ResourceType.AMBARI, null, RoleAuthorization.AMBARI_EDIT_STACK_REPOS)) {
                throw new AuthorizationException("The authenticated user does not have authorization to modify stack repositories");
              }
  
              final Object operatingSystems = propertyMap.get(SUBRESOURCE_OPERATING_SYSTEMS_PROPERTY_ID);
              final String operatingSystemsJson = gson.toJson(operatingSystems);
              try {
                entity.addRepoOsEntities(repositoryVersionHelper.parseOperatingSystems(operatingSystemsJson));
              } catch (Exception ex) {
                throw new AmbariException("Json structure for operating systems is incorrect", ex);
              }
            }
  
            if (StringUtils.isNotBlank(ObjectUtils.toString(propertyMap.get(REPOSITORY_VERSION_DISPLAY_NAME_PROPERTY_ID)))) {
              entity.setDisplayName(propertyMap.get(REPOSITORY_VERSION_DISPLAY_NAME_PROPERTY_ID).toString());
            }
  
            if(StringUtils.isNotBlank(ObjectUtils.toString(propertyMap.get(REPOSITORY_VERSION_HIDDEN_PROPERTY_ID)))){
              boolean isHidden = Boolean.valueOf(ObjectUtils.toString(propertyMap.get(REPOSITORY_VERSION_HIDDEN_PROPERTY_ID)));
              entity.setHidden(isHidden);
            }
  
            validateRepositoryVersion(repositoryVersionDAO, ambariMetaInfo, entity);
  
            repositoryVersionDAO.merge(entity);
          }
          return null;
        }
      });
  
      return getRequestStatus(null);
    }
  
    @Override
    protected RequestStatus deleteResourcesAuthorized(Request request, Predicate predicate)
        throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
      final Set<Map<String, Object>> propertyMaps = getPropertyMaps(predicate);
  
      final List<RepositoryVersionEntity> entitiesToBeRemoved = new ArrayList<>();
      for (Map<String, Object> propertyMap : propertyMaps) {
        final Long id;
        try {
          id = Long.parseLong(propertyMap.get(REPOSITORY_VERSION_ID_PROPERTY_ID).toString());
        } catch (Exception ex) {
          throw new SystemException("Repository version should have numerical id");
        }
  
        final RepositoryVersionEntity entity = repositoryVersionDAO.findByPK(id);
        if (entity == null) {
          throw new NoSuchResourceException("There is no repository version with id " + id);
        }
  
        final Set<RepositoryVersionState> forbiddenToDeleteStates = Sets.newHashSet(
            RepositoryVersionState.CURRENT,
            RepositoryVersionState.INSTALLED,
            RepositoryVersionState.INSTALLING);
  
        List<HostVersionEntity> hostVersions = hostVersionDAO.findByRepositoryAndStates(
            entity, forbiddenToDeleteStates);
  
        if (CollectionUtils.isNotEmpty(hostVersions)) {
          Map<RepositoryVersionState, Set<String>> hostsInUse = new HashMap<>();
  
          for (HostVersionEntity hostVersion : hostVersions) {
            if (!hostsInUse.containsKey(hostVersion.getState())) {
              hostsInUse.put(hostVersion.getState(), new HashSet<>());
            }
  
            hostsInUse.get(hostVersion.getState()).add(hostVersion.getHostName());
          }
  
          Set<String> errors = new HashSet<>();
          for (Entry<RepositoryVersionState, Set<String>> entry : hostsInUse.entrySet()) {
            errors.add(String.format("%s on %s", entry.getKey(), StringUtils.join(entry.getValue(), ", ")));
          }
  
  
          throw new SystemException(
              String.format("Repository version can't be deleted as it is used by the following hosts: %s",
                  StringUtils.join(errors, ';')));
        }
  
        entitiesToBeRemoved.add(entity);
      }
  
      for (RepositoryVersionEntity entity: entitiesToBeRemoved) {
        repositoryVersionDAO.remove(entity);
      }
  
      return getRequestStatus(null);
    }
  
    @Override
    protected Set<String> getPKPropertyIds() {
      return pkPropertyIds;
    }
  
    /**
     * Validates newly created repository versions to contain actual information.
     *
     * @throws AmbariException exception with error message
     */
    protected static void validateRepositoryVersion(RepositoryVersionDAO dao,
        AmbariMetaInfo metaInfo, RepositoryVersionEntity repositoryVersion) throws AmbariException {
      validateRepositoryVersion(dao, metaInfo, repositoryVersion, false);
    }
  
    /**
     * Validates newly created repository versions to contain actual information.  Optionally
     * skip url duplication.
     *
     * @throws AmbariException exception with error message
     */
    protected static void validateRepositoryVersion(RepositoryVersionDAO dao,
        AmbariMetaInfo metaInfo, RepositoryVersionEntity repositoryVersion, boolean skipUrlCheck) throws AmbariException {
      final StackId requiredStack = new StackId(repositoryVersion.getStack());
  
      final String requiredStackName = requiredStack.getStackName();
      final String requiredStackVersion = requiredStack.getStackVersion();
      final String requiredStackId = requiredStack.getStackId();
  
      // List of all repo urls that are already added at stack
      Set<String> existingRepoUrls = new HashSet<>();
      List<RepositoryVersionEntity> existingRepoVersions = dao.findByStack(requiredStack);
      for (RepositoryVersionEntity existingRepoVersion : existingRepoVersions) {
        for (RepoOsEntity operatingSystemEntity : existingRepoVersion.getRepoOsEntities()) {
          for (RepoDefinitionEntity repositoryEntity : operatingSystemEntity.getRepoDefinitionEntities()) {
            if (repositoryEntity.isUnique() && !existingRepoVersion.getId().equals(repositoryVersion.getId())) { // Allow modifying already defined repo version
              existingRepoUrls.add(repositoryEntity.getBaseUrl());
            }
          }
        }
      }
  
      // check that repositories contain only supported operating systems
      final Set<String> osSupported = new HashSet<>();
      for (OperatingSystemInfo osInfo: metaInfo.getOperatingSystems(requiredStackName, requiredStackVersion)) {
        osSupported.add(osInfo.getOsType());
      }
  
      final Set<String> osRepositoryVersion = new HashSet<>();
  
      for (RepoOsEntity os : repositoryVersion.getRepoOsEntities()) {
        osRepositoryVersion.add(os.getFamily());
  
        for (RepoDefinitionEntity repositoryEntity : os.getRepoDefinitionEntities()) {
          String baseUrl = repositoryEntity.getBaseUrl();
          if (!skipUrlCheck && os.isAmbariManaged() && existingRepoUrls.contains(baseUrl)) {
            throw new DuplicateResourceException("Base url " + URLCredentialsHider.hideCredentials(baseUrl) +
                    " is already defined for another repository version. " +
                    "Setting up base urls that contain the same versions of components will cause stack upgrade to fail.");
          }
        }
      }
  
      if (osRepositoryVersion.isEmpty()) {
        throw new AmbariException("At least one set of repositories for OS should be provided");
      }
  
      for (String os: osRepositoryVersion) {
        if (!osSupported.contains(os)) {
          throw new AmbariException("Operating system type " + os + " is not supported by stack " + requiredStackId);
        }
      }
  
      if (!RepositoryVersionEntity.isVersionInStack(repositoryVersion.getStackId(), repositoryVersion.getVersion())) {
        throw new AmbariException(MessageFormat.format("Version {0} needs to belong to stack {1}",
            repositoryVersion.getVersion(), repositoryVersion.getStackName() + "-" + repositoryVersion.getStackVersion()));
      }
    }
  
    /**
     * Transforms map of json properties to repository version entity.
     *
     * @param properties json map
     * @return constructed entity
     * @throws AmbariException if some properties are missing or json has incorrect structure
     */
    protected RepositoryVersionEntity toRepositoryVersionEntity(Map<String, Object> properties) throws AmbariException {
      final RepositoryVersionEntity entity = new RepositoryVersionEntity();
  
      final String stackName = properties.get(REPOSITORY_VERSION_STACK_NAME_PROPERTY_ID).toString();
      final String stackVersion = properties.get(REPOSITORY_VERSION_STACK_VERSION_PROPERTY_ID).toString();
  
      StackEntity stackEntity = stackDAO.find(stackName, stackVersion);
  
      entity.setDisplayName(properties.get(REPOSITORY_VERSION_DISPLAY_NAME_PROPERTY_ID).toString());
      entity.setStack(stackEntity);
  
      entity.setVersion(properties.get(REPOSITORY_VERSION_REPOSITORY_VERSION_PROPERTY_ID).toString());
      final Object operatingSystems = properties.get(SUBRESOURCE_OPERATING_SYSTEMS_PROPERTY_ID);
      final String operatingSystemsJson = gson.toJson(operatingSystems);
      try {
        entity.addRepoOsEntities(repositoryVersionHelper.parseOperatingSystems(operatingSystemsJson));
      } catch (Exception ex) {
        throw new AmbariException("Json structure for operating systems is incorrect", ex);
      }
      return entity;
    }
  
    protected StackId getStackInformationFromUrl(Map<String, Object> propertyMap) {
      if (propertyMap.containsKey(REPOSITORY_VERSION_STACK_NAME_PROPERTY_ID) && propertyMap.containsKey(REPOSITORY_VERSION_STACK_VERSION_PROPERTY_ID)) {
        return new StackId(propertyMap.get(REPOSITORY_VERSION_STACK_NAME_PROPERTY_ID).toString(), propertyMap.get(REPOSITORY_VERSION_STACK_VERSION_PROPERTY_ID).toString());
      }
      return null;
    }
  
    @Override
    protected ResourceType getResourceType(Request request, Predicate predicate) {
      // This information is not associated with any particular resource
      return null;
    }
  }
  