/**
 * 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.view;

import com.google.common.collect.Sets;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.persist.Transactional;
import org.apache.ambari.server.api.resources.ResourceInstanceFactoryImpl;
import org.apache.ambari.server.api.resources.SubResourceDefinition;
import org.apache.ambari.server.api.resources.ViewExternalSubResourceDefinition;
import org.apache.ambari.server.api.services.AmbariMetaInfo;
import org.apache.ambari.server.api.services.ViewExternalSubResourceService;
import org.apache.ambari.server.api.services.ViewSubResourceService;
import org.apache.ambari.server.configuration.ComponentSSLConfiguration;
import org.apache.ambari.server.configuration.Configuration;
import org.apache.ambari.server.controller.AmbariServer;
import org.apache.ambari.server.controller.AmbariSessionManager;
import org.apache.ambari.server.controller.spi.Resource;
import org.apache.ambari.server.controller.spi.ResourceProvider;
import org.apache.ambari.server.orm.dao.MemberDAO;
import org.apache.ambari.server.orm.dao.PrivilegeDAO;
import org.apache.ambari.server.orm.dao.ResourceDAO;
import org.apache.ambari.server.orm.dao.ResourceTypeDAO;
import org.apache.ambari.server.orm.dao.UserDAO;
import org.apache.ambari.server.orm.dao.ViewDAO;
import org.apache.ambari.server.orm.dao.ViewInstanceDAO;
import org.apache.ambari.server.orm.entities.GroupEntity;
import org.apache.ambari.server.orm.entities.MemberEntity;
import org.apache.ambari.server.orm.entities.PermissionEntity;
import org.apache.ambari.server.orm.entities.PrincipalEntity;
import org.apache.ambari.server.orm.entities.PrivilegeEntity;
import org.apache.ambari.server.orm.entities.ResourceEntity;
import org.apache.ambari.server.orm.entities.ResourceTypeEntity;
import org.apache.ambari.server.orm.entities.UserEntity;
import org.apache.ambari.server.orm.entities.ViewEntity;
import org.apache.ambari.server.orm.entities.ViewEntityEntity;
import org.apache.ambari.server.orm.entities.ViewInstanceDataEntity;
import org.apache.ambari.server.orm.entities.ViewInstanceEntity;
import org.apache.ambari.server.orm.entities.ViewParameterEntity;
import org.apache.ambari.server.orm.entities.ViewResourceEntity;
import org.apache.ambari.server.security.SecurityHelper;
import org.apache.ambari.server.security.authorization.AmbariGrantedAuthority;
import org.apache.ambari.server.utils.VersionUtils;
import org.apache.ambari.server.view.configuration.EntityConfig;
import org.apache.ambari.server.view.configuration.InstanceConfig;
import org.apache.ambari.server.view.configuration.ParameterConfig;
import org.apache.ambari.server.view.configuration.PermissionConfig;
import org.apache.ambari.server.view.configuration.PersistenceConfig;
import org.apache.ambari.server.view.configuration.PropertyConfig;
import org.apache.ambari.server.view.configuration.ResourceConfig;
import org.apache.ambari.server.view.configuration.ViewConfig;
import org.apache.ambari.server.view.validation.ValidationException;
import org.apache.ambari.view.validation.Validator;
import org.apache.ambari.view.Masker;
import org.apache.ambari.view.SystemException;
import org.apache.ambari.view.View;
import org.apache.ambari.view.ViewContext;
import org.apache.ambari.view.ViewDefinition;
import org.apache.ambari.view.ViewResourceHandler;
import org.apache.ambari.view.events.Event;
import org.apache.ambari.view.events.Listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.GrantedAuthority;

import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import java.beans.IntrospectionException;
import java.io.File;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Registry for view and view instance definitions.
 */
@Singleton
public class ViewRegistry {

  /**
   * Constants
   */
  private static final String EXTRACTED_ARCHIVES_DIR = "work";
  private static final String EXTRACT_COMMAND = "extract";
  private static final String ALL_VIEWS_REG_EXP = ".*";
  protected static final int DEFAULT_REQUEST_CONNECT_TIMEOUT = 5000;
  protected static final int DEFAULT_REQUEST_READ_TIMEOUT    = 10000;
  private static final String VIEW_AMBARI_VERSION_REGEXP = "^((\\d+\\.)?)*(\\*|\\d+)$";

  /**
   * Thread pool
   */
  private static ExecutorService executorService;

  /**
   * Mapping of view names to view definitions.
   */
  private Map<String, ViewEntity> viewDefinitions = new HashMap<String, ViewEntity>();

  /**
   * Mapping of view instances to view definition and instance name.
   */
  private Map<ViewEntity, Map<String, ViewInstanceEntity>> viewInstanceDefinitions =
      new HashMap<ViewEntity, Map<String, ViewInstanceEntity>>();

  /**
   * Mapping of view names to sub-resources.
   */
  private final Map<String, Set<SubResourceDefinition>> subResourceDefinitionsMap =
      new ConcurrentHashMap<String, Set<SubResourceDefinition>>();

  /**
   * Mapping of view types to resource providers.
   */
  private final Map<Resource.Type, ResourceProvider> resourceProviders =
      new ConcurrentHashMap<Resource.Type, ResourceProvider>();

  /**
   * Mapping of view names to registered listeners.
   */
  private final Map<String, Set<Listener>> listeners =
      new ConcurrentHashMap<String, Set<Listener>>();

  /**
   * The singleton view registry instance.
   */
  private static ViewRegistry singleton;

  /**
   * The logger.
   */
  protected final static Logger LOG = LoggerFactory.getLogger(ViewRegistry.class);

  /**
   * View data access object.
   */
  @Inject
  ViewDAO viewDAO;

  /**
   * View instance data access object.
   */
  @Inject
  ViewInstanceDAO instanceDAO;

  /**
   * User data access object.
   */
  @Inject
  UserDAO userDAO;

  /**
   * Group member data access object.
   */
  @Inject
  MemberDAO memberDAO;

  /**
   * Privilege data access object.
   */
  @Inject
  PrivilegeDAO privilegeDAO;

  /**
   * Helper with security related utilities.
   */
  @Inject
  SecurityHelper securityHelper;

  /**
   * Resource data access object.
   */
  @Inject
  ResourceDAO resourceDAO;

  /**
   * Resource type data access object.
   */
  @Inject
  ResourceTypeDAO resourceTypeDAO;

  /**
   * Ambari meta info.
   */
  @Inject
  Provider<AmbariMetaInfo> ambariMetaInfo;

  /**
   * Ambari configuration.
   */
  @Inject
  Configuration configuration;

  /**
   * The handler list.
   */
  @Inject
  ViewInstanceHandlerList handlerList;

  /**
   * The view extractor;
   */
  @Inject
  ViewExtractor extractor;

  /**
   * The view archive utility.
   */
  @Inject
  ViewArchiveUtility archiveUtility;

  /**
   * The Ambari session manager.
   */
  @Inject
  AmbariSessionManager ambariSessionManager;


  // ----- ViewRegistry ------------------------------------------------------

  /**
   * Registry main method.
   *
   * @param args  the command line arguments
   */
  public static void main(String[] args) {

    if (args.length >= 2) {
      String archivePath = args[1];

      try {
        Injector injector = Guice.createInjector();

        ViewExtractor      extractor      = injector.getInstance(ViewExtractor.class);
        ViewArchiveUtility archiveUtility = injector.getInstance(ViewArchiveUtility.class);
        Configuration      configuration  = injector.getInstance(Configuration.class);

        if (args[0].equals(EXTRACT_COMMAND)) {
          if (extractViewArchive(archivePath, extractor, archiveUtility, configuration, true)) {
            System.exit(0);
          }
        }
      } catch (Exception e) {
        String msg = "Caught exception extracting view archive " + archivePath + ".";
        LOG.error(msg, e);
        System.exit(2);
      }
    }
    System.exit(1);
  }

  /**
   * Get the collection of all the view definitions.
   *
   * @return the collection of view definitions
   */
  public Collection<ViewEntity> getDefinitions() {
    return viewDefinitions.values();
  }

  /**
   * Get a view definition for the given name.
   *
   * @param viewName  the view name
   * @param version   the version
   *
   * @return the view definition for the given name
   */
  public ViewEntity getDefinition(String viewName, String version) {
    return getDefinition(ViewEntity.getViewName(viewName, version));
  }

  /**
   * Get the view definition for the given resource type.
   *
   * @param resourceTypeEntity  the resource type
   *
   * @return the view definition for the given resource type or null
   */
  public ViewEntity getDefinition(ResourceTypeEntity resourceTypeEntity) {

    for (ViewEntity viewEntity : viewDefinitions.values()) {
      if (viewEntity.isDeployed()) {
        if (viewEntity.getResourceType().equals(resourceTypeEntity)) {
          return viewEntity;
        }
      }
    }
    return null;
  }

  /**
   * Add a view definition to the registry.
   *
   * @param definition  the definition
   */
  public void addDefinition(ViewEntity definition) {
    viewDefinitions.put(definition.getName(), definition);
  }

  /**
   * Get the collection of view instances for the given view definition.
   *
   * @param definition  the view definition
   *
   * @return the collection of view instances for the view definition
   */
  public Collection<ViewInstanceEntity> getInstanceDefinitions(ViewEntity definition) {
    if (definition != null) {
      Map<String, ViewInstanceEntity> instanceEntityMap = viewInstanceDefinitions.get(definition);
      if (instanceEntityMap != null) {
        return instanceEntityMap.values();
      }
    }
    return Collections.emptyList();
  }

  /**
   * Get the instance definition for the given view name and instance name.
   *
   * @param viewName      the view name
   * @param version       the version
   * @param instanceName  the instance name
   *
   * @return the view instance definition for the given view and instance name
   */
  public ViewInstanceEntity getInstanceDefinition(String viewName, String version, String instanceName) {
    Map<String, ViewInstanceEntity> viewInstanceDefinitionMap =
        viewInstanceDefinitions.get(getDefinition(viewName, version));

    return viewInstanceDefinitionMap == null ? null : viewInstanceDefinitionMap.get(instanceName);
  }

  /**
   * Add an instance definition for the given view definition.
   *
   * @param definition          the owning view definition
   * @param instanceDefinition  the instance definition
   */
  public void addInstanceDefinition(ViewEntity definition, ViewInstanceEntity instanceDefinition) {
    Map<String, ViewInstanceEntity> instanceDefinitions = viewInstanceDefinitions.get(definition);
    if (instanceDefinitions == null) {
      instanceDefinitions = new HashMap<String, ViewInstanceEntity>();
      viewInstanceDefinitions.put(definition, instanceDefinitions);
    }

    View view = definition.getView();
    if (view != null) {
      view.onCreate(instanceDefinition);
    }
    instanceDefinitions.put(instanceDefinition.getName(), instanceDefinition);
  }

  /**
   * Remove an instance definition for the given view definition.
   *
   * @param definition    the owning view definition
   * @param instanceName  the instance name
   */
  public void removeInstanceDefinition(ViewEntity definition, String instanceName) {
    Map<String, ViewInstanceEntity> instanceDefinitions = viewInstanceDefinitions.get(definition);
    if (instanceDefinitions != null) {

      ViewInstanceEntity instanceDefinition = instanceDefinitions.get(instanceName);
      if (instanceDefinition != null) {
        View view = definition.getView();
        if (view != null) {
          view.onDestroy(instanceDefinition);
        }
        instanceDefinitions.remove(instanceName);
      }
    }
  }

  /**
   * Init the singleton instance.
   *
   * @param singleton  the view registry
   */
  public static void initInstance(ViewRegistry singleton) {
    ViewRegistry.singleton = singleton;
  }

  /**
   * Get the view registry singleton.
   *
   * @return  the view registry
   */
  public static ViewRegistry getInstance() {
    return singleton;
  }

  /**
   * Get the sub-resource definitions for the given view name.
   *
   * @param viewName  the instance name
   * @param version   the version
   *
   * @return the set of sub-resource definitions
   */
  public Set<SubResourceDefinition> getSubResourceDefinitions(
      String viewName, String version) {

    viewName = ViewEntity.getViewName(viewName, version);

    return subResourceDefinitionsMap.get(viewName);
  }
  /**
   * Read all view archives.
   */
  public void readViewArchives() {
    readViewArchives(false, true, ALL_VIEWS_REG_EXP, true);
  }

  /**
   * Read only view archives with names corresponding to given regular expression.
   *
   * @param viewNameRegExp view name regular expression
   */
  public void readViewArchives(String viewNameRegExp) {
    readViewArchives(false, false, viewNameRegExp, false);
  }

  /**
   * Determine whether or not the given view instance exists.
   *
   * @param instanceEntity  the view instance entity
   *
   * @return true if the the given view instance exists; false otherwise
   */
  public boolean instanceExists(ViewInstanceEntity instanceEntity) {

    ViewEntity viewEntity = getDefinition(instanceEntity.getViewName());

    return viewEntity != null &&
        (getInstanceDefinition(viewEntity.getCommonName(), viewEntity.getVersion(), instanceEntity.getName()) != null);
  }

  /**
   * Install the given view instance with its associated view.
   *
   * @param instanceEntity  the view instance entity
   *
   * @throws ValidationException       if the given instance fails the validation checks
   * @throws IllegalArgumentException  if the view associated with the given instance
   *                                   does not exist
   * @throws SystemException           if the instance can not be installed
   */
  @Transactional
  public void installViewInstance(ViewInstanceEntity instanceEntity)
      throws ValidationException, IllegalArgumentException, SystemException {
    ViewEntity viewEntity = getDefinition(instanceEntity.getViewName());

    if (viewEntity != null) {
      String instanceName = instanceEntity.getName();
      String viewName     = viewEntity.getCommonName();
      String version      = viewEntity.getVersion();

      if (getInstanceDefinition(viewName, version, instanceName) == null) {
        if (LOG.isDebugEnabled()) {
          LOG.debug("Creating view instance " + viewName + "/" +
              version + "/" + instanceName);
        }

        instanceEntity.validate(viewEntity, Validator.ValidationContext.PRE_CREATE);

        ResourceTypeEntity resourceTypeEntity = resourceTypeDAO.findByName(ViewEntity.getViewName(viewName, version));
        // create an admin resource to represent this view instance
        instanceEntity.setResource(createViewInstanceResource(resourceTypeEntity));

        instanceDAO.merge(instanceEntity);

        ViewInstanceEntity persistedInstance = instanceDAO.findByName(ViewEntity.getViewName(viewName, version), instanceName);
        if (persistedInstance == null) {
          String message = "Instance  " + instanceEntity.getViewName() + " can not be found.";

          LOG.error(message);
          throw new IllegalStateException(message);
        }
        instanceEntity.setViewInstanceId(persistedInstance.getViewInstanceId());
        instanceEntity.setResource(persistedInstance.getResource());

        try {
          // bind the view instance to a view
          bindViewInstance(viewEntity, instanceEntity);
        } catch (Exception e) {
          String message = "Caught exception installing view instance.";
          LOG.error(message, e);
          throw new IllegalStateException(message, e);
        }
        // update the registry
        addInstanceDefinition(viewEntity, instanceEntity);

        // add the web app context
        handlerList.addViewInstance(instanceEntity);
      }
    } else {
      String message = "Attempt to install an instance for an unknown view " +
          instanceEntity.getViewName() + ".";

      LOG.error(message);
      throw new IllegalArgumentException(message);
    }
  }

  /**
   * Update a view instance for the view with the given view name.
   *
   * @param instanceEntity  the view instance entity
   *
   * @throws ValidationException   if the given instance fails the validation checks
   * @throws SystemException       if the instance can not be updated
   */
  public void updateViewInstance(ViewInstanceEntity instanceEntity)
      throws ValidationException, SystemException {
    ViewEntity viewEntity = getDefinition(instanceEntity.getViewName());

    if (viewEntity != null) {
      instanceEntity.validate(viewEntity, Validator.ValidationContext.PRE_UPDATE);
      instanceDAO.merge(instanceEntity);

      syncViewInstance(instanceEntity);
    }
  }

  /**
   * Get a view instance entity for the given view name and instance name.
   *
   * @param viewName      the view name
   * @param instanceName  the instance name
   *
   * @return a view instance entity for the given view name and instance name.
   */
  public ViewInstanceEntity getViewInstanceEntity(String viewName, String instanceName) {
    return instanceDAO.findByName(viewName, instanceName);
  }

  /**
   * Uninstall a view instance for the view with the given view name.
   *
   * @param instanceEntity  the view instance entity
   * @throws IllegalStateException if the given instance is not in a valid state
   */
  @Transactional
  public void uninstallViewInstance(ViewInstanceEntity instanceEntity) throws IllegalStateException {
    ViewEntity viewEntity = getDefinition(instanceEntity.getViewName());

    if (viewEntity != null) {
      String instanceName = instanceEntity.getName();
      String viewName     = viewEntity.getCommonName();
      String version      = viewEntity.getVersion();

      if (getInstanceDefinition(viewName, version, instanceName) != null) {
        if (instanceEntity.isXmlDriven()) {
          throw new IllegalStateException("View instances defined via xml can't be deleted through api requests");
        }
        if (LOG.isDebugEnabled()) {
          LOG.debug("Deleting view instance " + viewName + "/" +
              version + "/" +instanceName);
        }
        List<PrivilegeEntity> instancePrivileges = privilegeDAO.findByResourceId(instanceEntity.getResource().getId());
        for (PrivilegeEntity privilegeEntity : instancePrivileges) {
          removePrivilegeEntity(privilegeEntity);
        }
        instanceDAO.remove(instanceEntity);
        viewEntity.removeInstanceDefinition(instanceName);
        removeInstanceDefinition(viewEntity, instanceName);

        // remove the web app context
        handlerList.removeViewInstance(instanceEntity);
      }
    }
  }

  /**
   * Remove the data entry keyed by the given key from the given instance entity.
   *
   * @param instanceEntity  the instance entity
   * @param key             the data key
   */
  @Transactional
  public void removeInstanceData(ViewInstanceEntity instanceEntity, String key) {
    ViewInstanceDataEntity dataEntity = instanceEntity.getInstanceData(key);
    if (dataEntity != null) {
      instanceDAO.removeData(dataEntity);
    }
    instanceEntity.removeInstanceData(key);
    instanceDAO.merge(instanceEntity);
  }

  /**
   * Notify any registered listeners of the given event.
   *
   * @param event  the event
   */
  public void fireEvent(Event event) {

    ViewDefinition subject = event.getViewSubject();

    fireEvent(event, subject.getViewName());
    fireEvent(event, ViewEntity.getViewName(subject.getViewName(), subject.getVersion()));
  }

  /**
   * Register the given listener to listen for events from the view identified by the given name and version.
   *
   * @param listener     the listener
   * @param viewName     the view name
   * @param viewVersion  the view version; null indicates all versions
   */
  public synchronized void registerListener(Listener listener, String viewName, String viewVersion) {

    String name = viewVersion == null ? viewName : ViewEntity.getViewName(viewName, viewVersion);

    Set<Listener> listeners = this.listeners.get(name);

    if (listeners == null) {
      listeners = Sets.newSetFromMap(new ConcurrentHashMap<Listener, Boolean>());
      this.listeners.put(name, listeners);
    }

    listeners.add(listener);
  }

  /**
   * Un-register the given listener from the view identified by the given name and version.
   *
   * @param listener     the listener
   * @param viewName     the view name
   * @param viewVersion  the view version; null indicates all versions
   */
  public synchronized void unregisterListener(Listener listener, String viewName, String viewVersion) {

    String name = viewVersion == null ? viewName : ViewEntity.getViewName(viewName, viewVersion);

    Set<Listener> listeners = this.listeners.get(name);

    if (listeners != null) {
      listeners.remove(listener);
    }
  }

  /**
   * Determine whether or not the access specified by the given permission
   * is permitted for the given user on the view instance identified by
   * the given resource.
   *
   * @param permissionEntity  the permission entity
   * @param resourceEntity    the resource entity
   * @param userName          the user name
   *
   * @return true if the access specified by the given permission
   *         is permitted for the given user.
   */
  public boolean hasPermission(PermissionEntity permissionEntity, ResourceEntity resourceEntity, String userName) {

    UserEntity userEntity = userDAO.findUserByName(userName);

    if (userEntity == null) {
      return false;
    }

    if (privilegeDAO.exists(userEntity.getPrincipal(), resourceEntity, permissionEntity)) {
      return true;
    }

    List<MemberEntity> memberEntities = memberDAO.findAllMembersByUser(userEntity);

    for (MemberEntity memberEntity : memberEntities) {

      GroupEntity groupEntity = memberEntity.getGroup();

      if (privilegeDAO.exists(groupEntity.getPrincipal(), resourceEntity, permissionEntity)) {
        return true;
      }
    }
    return false;
  }

  /**
   * Determine whether or not access to the view instance resource identified
   * by the given instance name should be allowed based on the permissions
   * granted to the current user.
   *
   * @param viewName      the view name
   * @param version       the view version
   * @param instanceName  the name of the view instance resource
   * @param readOnly      indicate whether or not this is for a read only operation
   *
   * @return true if the access to the view instance is allowed
   */
  public boolean checkPermission(String viewName, String version, String instanceName, boolean readOnly) {

    ViewInstanceEntity instanceEntity =
        instanceName == null ? null : getInstanceDefinition(viewName, version, instanceName);

    return checkPermission(instanceEntity, readOnly);
  }

  /**
   * Determine whether or not access to the given view instance should be allowed based
   * on the permissions granted to the current user.
   *
   * @param instanceEntity  the view instance entity
   * @param readOnly        indicate whether or not this is for a read only operation
   *
   * @return true if the access to the view instance is allowed
   */
  public boolean checkPermission(ViewInstanceEntity instanceEntity, boolean readOnly) {

    ResourceEntity resourceEntity = instanceEntity == null ? null : instanceEntity.getResource();

    return !configuration.getApiAuthentication() ||
        (resourceEntity == null && readOnly) || checkAuthorization(resourceEntity);
  }

  /**
   * Determine whether or not the current view user has admin rights.
   *
   * @return true if the current view user is an admin
   */
  public boolean checkAdmin() {
    return checkAuthorization(null);
  }

  /**
   * Determine whether or not the given view definition resource should be included
   * based on the permissions granted to the current user.
   *
   * @param definitionEntity  the view definition entity
   *
   * @return true if the view instance should be included based on the permissions of the current user
   */
  public boolean includeDefinition(ViewEntity definitionEntity) {

    if (checkPermission(null, false)) {
      return true;
    }

    for (ViewInstanceEntity instanceEntity: definitionEntity.getInstances()) {
      if (checkPermission(instanceEntity, true) ) {
        return true;
      }
    }
    return false;
  }

  /**
   * Set the properties of the given view instance from the given property set.
   *
   * @param instanceEntity  the view instance entity
   * @param properties      the view instance properties
   * @param viewConfig      the view configuration
   * @param classLoader     the class loader for the view
   *
   * @throws SystemException if the view instance properties can not be set
   */
  public void setViewInstanceProperties(ViewInstanceEntity instanceEntity, Map<String, String> properties,
                                        ViewConfig viewConfig, ClassLoader classLoader) throws SystemException {
    try {
      Masker masker = getMasker(viewConfig.getMaskerClass(classLoader));

      Map<String, ParameterConfig> parameterConfigMap = new HashMap<String, ParameterConfig>();
      for (ParameterConfig paramConfig : viewConfig.getParameters()) {
        parameterConfigMap.put(paramConfig.getName(), paramConfig);
      }
      for (Map.Entry<String, String> entry : properties.entrySet()) {
        String name  = entry.getKey();
        String value = entry.getValue();

        ParameterConfig parameterConfig = parameterConfigMap.get(name);

        if (parameterConfig != null && parameterConfig.isMasked()) {
          value = masker.mask(value);
        }
        instanceEntity.putProperty(name, value);
      }
    } catch (Exception e) {
      throw new SystemException("Caught exception while setting instance property.", e);
    }
  }


  // ----- helper methods ----------------------------------------------------

  /**
   * Clear the registry.
   */
  protected void clear() {
    viewDefinitions.clear();
    viewInstanceDefinitions.clear();
    subResourceDefinitionsMap.clear();
    listeners.clear();
  }

  /**
   * Get the view resource provider mapping.
   *
   * @return the map of view resource providers
   */
  protected Map<Resource.Type, ResourceProvider> getResourceProviders() {
    return resourceProviders;
  }

  // get a view entity for the given internal view name
  private ViewEntity getDefinition(String viewName) {
    return viewDefinitions.get(viewName);
  }

  // setup the given view definition
  protected ViewEntity setupViewDefinition(ViewEntity viewDefinition, ClassLoader cl)
      throws ClassNotFoundException, IntrospectionException {

    ViewConfig viewConfig = viewDefinition.getConfiguration();

    viewDefinition.setClassLoader(cl);

    List<ParameterConfig> parameterConfigurations = viewConfig.getParameters();

    Collection<ViewParameterEntity> parameters = new HashSet<ViewParameterEntity>();

    String viewName = viewDefinition.getName();

    for (ParameterConfig parameterConfiguration : parameterConfigurations) {
      ViewParameterEntity viewParameterEntity =  new ViewParameterEntity();

      viewParameterEntity.setViewName(viewName);
      viewParameterEntity.setName(parameterConfiguration.getName());
      viewParameterEntity.setDescription(parameterConfiguration.getDescription());
      viewParameterEntity.setLabel(parameterConfiguration.getLabel());
      viewParameterEntity.setPlaceholder(parameterConfiguration.getPlaceholder());
      viewParameterEntity.setDefaultValue(parameterConfiguration.getDefaultValue());
      viewParameterEntity.setRequired(parameterConfiguration.isRequired());
      viewParameterEntity.setMasked(parameterConfiguration.isMasked());
      viewParameterEntity.setViewEntity(viewDefinition);
      parameters.add(viewParameterEntity);
    }
    viewDefinition.setParameters(parameters);

    List<ResourceConfig> resourceConfigurations = viewConfig.getResources();

    Resource.Type externalResourceType = viewDefinition.getExternalResourceType();

    ViewExternalSubResourceProvider viewExternalSubResourceProvider =
        new ViewExternalSubResourceProvider(externalResourceType, viewDefinition);
    viewDefinition.addResourceProvider(externalResourceType, viewExternalSubResourceProvider);

    resourceProviders.put(externalResourceType, viewExternalSubResourceProvider);

    ResourceInstanceFactoryImpl.addResourceDefinition(externalResourceType,
        new ViewExternalSubResourceDefinition(externalResourceType));

    Collection<ViewResourceEntity> resources = new HashSet<ViewResourceEntity>();
    for (ResourceConfig resourceConfiguration : resourceConfigurations) {
      ViewResourceEntity viewResourceEntity = new ViewResourceEntity();

      viewResourceEntity.setViewName(viewName);
      viewResourceEntity.setName(resourceConfiguration.getName());
      viewResourceEntity.setPluralName(resourceConfiguration.getPluralName());
      viewResourceEntity.setIdProperty(resourceConfiguration.getIdProperty());
      viewResourceEntity.setResource(resourceConfiguration.getResource());
      viewResourceEntity.setService(resourceConfiguration.getService());
      viewResourceEntity.setProvider(resourceConfiguration.getProvider());
      viewResourceEntity.setSubResourceNames(resourceConfiguration.getSubResourceNames());
      viewResourceEntity.setViewEntity(viewDefinition);

      ViewSubResourceDefinition resourceDefinition = new ViewSubResourceDefinition(viewDefinition, resourceConfiguration);
      viewDefinition.addResourceDefinition(resourceDefinition);

      Resource.Type type = resourceDefinition.getType();
      viewDefinition.addResourceConfiguration(type, resourceConfiguration);

      if (resourceConfiguration.isExternal()) {
        viewExternalSubResourceProvider.addResourceName(resourceConfiguration.getName());
      } else {
        ResourceInstanceFactoryImpl.addResourceDefinition(type, resourceDefinition);

        Class<?> clazz      = resourceConfiguration.getResourceClass(cl);
        String   idProperty = resourceConfiguration.getIdProperty();

        ViewSubResourceProvider provider = new ViewSubResourceProvider(type, clazz, idProperty, viewDefinition);
        viewDefinition.addResourceProvider(type, provider);
        resourceProviders.put(type, provider);

        resources.add(viewResourceEntity);
      }
      viewDefinition.setResources(resources);
    }

    ResourceTypeEntity resourceTypeEntity = new ResourceTypeEntity();
    resourceTypeEntity.setName(viewName);

    viewDefinition.setResourceType(resourceTypeEntity);

    List<PermissionConfig> permissionConfigurations = viewConfig.getPermissions();

    Collection<PermissionEntity> permissions = new HashSet<PermissionEntity>();
    for (PermissionConfig permissionConfiguration : permissionConfigurations) {
      PermissionEntity permissionEntity =  new PermissionEntity();

      permissionEntity.setPermissionName(permissionConfiguration.getName());
      permissionEntity.setResourceType(resourceTypeEntity);
      permissions.add(permissionEntity);
    }
    viewDefinition.setPermissions(permissions);

    View view = null;
    if (viewConfig.getView() != null) {
      view = getView(viewConfig.getViewClass(cl), new ViewContextImpl(viewDefinition, this));
    }
    viewDefinition.setView(view);
    Validator validator = null;
    if (viewConfig.getValidator() != null) {
      validator = getValidator(viewConfig.getValidatorClass(cl), new ViewContextImpl(viewDefinition, this));
    }
    viewDefinition.setValidator(validator);
    viewDefinition.setMask(viewConfig.getMasker());

    Set<SubResourceDefinition> subResourceDefinitions = new HashSet<SubResourceDefinition>();
    for (Resource.Type type : viewDefinition.getViewResourceTypes()) {
      subResourceDefinitions.add(new SubResourceDefinition(type));
    }
    subResourceDefinitionsMap.put(viewName, subResourceDefinitions);

    return viewDefinition;
  }

  // create a new view instance definition
  protected ViewInstanceEntity createViewInstanceDefinition(ViewConfig viewConfig, ViewEntity viewDefinition,
                                                            InstanceConfig instanceConfig)
      throws ValidationException, ClassNotFoundException, SystemException {
    ViewInstanceEntity viewInstanceDefinition =
        new ViewInstanceEntity(viewDefinition, instanceConfig);

    Map<String, String> properties = new HashMap<String, String>();

    for (PropertyConfig propertyConfig : instanceConfig.getProperties()) {
      properties.put(propertyConfig.getKey(), propertyConfig.getValue());
    }
    setViewInstanceProperties(viewInstanceDefinition, properties, viewConfig, viewDefinition.getClassLoader());
    viewInstanceDefinition.validate(viewDefinition, Validator.ValidationContext.PRE_CREATE);

    bindViewInstance(viewDefinition, viewInstanceDefinition);
    return viewInstanceDefinition;
  }

  // bind a view instance definition to the given view definition
  protected void bindViewInstance(ViewEntity viewDefinition,
                                   ViewInstanceEntity viewInstanceDefinition)
      throws ClassNotFoundException {
    viewInstanceDefinition.setViewEntity(viewDefinition);

    ViewContext viewInstanceContext = new ViewContextImpl(viewInstanceDefinition, this);

    ViewExternalSubResourceService externalSubResourceService =
        new ViewExternalSubResourceService(viewDefinition.getExternalResourceType(), viewInstanceDefinition);

    viewInstanceDefinition.addService(ResourceConfig.EXTERNAL_RESOURCE_PLURAL_NAME, externalSubResourceService);

    Collection<ViewSubResourceDefinition> resourceDefinitions = viewDefinition.getResourceDefinitions().values();
    for (ViewSubResourceDefinition resourceDefinition : resourceDefinitions) {

      Resource.Type  type           = resourceDefinition.getType();
      ResourceConfig resourceConfig = resourceDefinition.getResourceConfiguration();

      ViewResourceHandler viewResourceService = new ViewSubResourceService(type, viewInstanceDefinition);

      ClassLoader cl = viewDefinition.getClassLoader();

      Object service = getService(resourceConfig.getServiceClass(cl), viewResourceService, viewInstanceContext);

      if (resourceConfig.isExternal()) {
        externalSubResourceService.addResourceService(resourceConfig.getName(), service);
      } else {
        viewInstanceDefinition.addService(viewDefinition.getResourceDefinition(type).getPluralName(),service);
        viewInstanceDefinition.addResourceProvider(type,
            getProvider(resourceConfig.getProviderClass(cl), viewInstanceContext));
      }
    }

    setPersistenceEntities(viewInstanceDefinition);

    viewDefinition.addInstanceDefinition(viewInstanceDefinition);
  }

  // Set the entities defined in the view persistence element for the given view instance
  private static void setPersistenceEntities(ViewInstanceEntity viewInstanceDefinition) {
    ViewEntity        viewDefinition    = viewInstanceDefinition.getViewEntity();
    ViewConfig        viewConfig        = viewDefinition.getConfiguration();

    Collection<ViewEntityEntity> entities = new HashSet<ViewEntityEntity>();

    if (viewConfig != null) {
      PersistenceConfig persistenceConfig = viewConfig.getPersistence();

      if (persistenceConfig != null) {
        for (EntityConfig entityConfiguration : persistenceConfig.getEntities()) {
          ViewEntityEntity viewEntityEntity = new ViewEntityEntity();

          viewEntityEntity.setViewName(viewDefinition.getName());
          viewEntityEntity.setViewInstanceName(viewInstanceDefinition.getName());
          viewEntityEntity.setClassName(entityConfiguration.getClassName());
          viewEntityEntity.setIdProperty(entityConfiguration.getIdProperty());
          viewEntityEntity.setViewInstance(viewInstanceDefinition);

          entities.add(viewEntityEntity);
        }
      }
    }
    viewInstanceDefinition.setEntities(entities);
  }

  // get the given service class from the given class loader; inject a handler and context
  private static <T> T getService(Class<T> clazz,
                                  final ViewResourceHandler viewResourceHandler,
                                  final ViewContext viewInstanceContext) {
    Injector viewInstanceInjector = Guice.createInjector(new AbstractModule() {
      @Override
      protected void configure() {
        bind(ViewResourceHandler.class)
            .toInstance(viewResourceHandler);
        bind(ViewContext.class)
            .toInstance(viewInstanceContext);
      }
    });
    return viewInstanceInjector.getInstance(clazz);
  }

  // get the given resource provider class from the given class loader; inject a context
  private static org.apache.ambari.view.ResourceProvider getProvider(
      Class<? extends org.apache.ambari.view.ResourceProvider> clazz,
      final ViewContext viewInstanceContext) {
    Injector viewInstanceInjector = Guice.createInjector(new AbstractModule() {
      @Override
      protected void configure() {
        bind(ViewContext.class)
            .toInstance(viewInstanceContext);
      }
    });
    return viewInstanceInjector.getInstance(clazz);
  }

  // get the given view class from the given class loader; inject a context
  private static View getView(Class<? extends View> clazz,
                              final ViewContext viewContext) {
    Injector viewInstanceInjector = Guice.createInjector(new AbstractModule() {
      @Override
      protected void configure() {
        bind(ViewContext.class)
            .toInstance(viewContext);
      }
    });
    return viewInstanceInjector.getInstance(clazz);
  }

  // get the given view validator class from the given class loader; inject a context
  private static Validator getValidator(Class<? extends Validator> clazz,
                              final ViewContext viewContext) {
    Injector viewInstanceInjector = Guice.createInjector(new AbstractModule() {
      @Override
      protected void configure() {
        bind(ViewContext.class)
            .toInstance(viewContext);
      }
    });
    return viewInstanceInjector.getInstance(clazz);
  }

  // create masker from given class; probably replace with injector later
  private static Masker getMasker(Class<? extends Masker> clazz) {
    try {
      return clazz.newInstance();
    } catch (Exception e) {
      LOG.error("Could not create masker instance", e);
    }
    return null;
  }

  // remove undeployed views from the ambari db
  private void removeUndeployedViews() {
    for (ViewEntity viewEntity : viewDAO.findAll()) {
      String name = viewEntity.getName();
      if (!ViewRegistry.getInstance().viewDefinitions.containsKey(name)) {
        try {
          viewDAO.remove(viewEntity);
        } catch (Exception e) {
          LOG.error("Caught exception undeploying view " + viewEntity.getName(), e);
        }
      }
    }
  }

  /**
   * Sync given view with data in DB. Ensures that view data in DB is updated,
   * all instances changes from xml config are reflected to DB
   *
   * @param view                 view config from xml
   * @param instanceDefinitions  view instances from xml
   *
   * @throws Exception if the view can not be synced
   */
  private void syncView(ViewEntity view,
                        Set<ViewInstanceEntity> instanceDefinitions)
      throws Exception {

    String             viewName      = view.getName();
    ViewEntity         persistedView = viewDAO.findByName(viewName);
    ResourceTypeEntity resourceType  = view.getResourceType();

    // if the view is not yet persisted ...
    if (persistedView == null) {
      if (LOG.isDebugEnabled()) {
        LOG.debug("Creating View " + viewName + ".");
      }

      // get or create an admin resource type to represent this view
      ResourceTypeEntity resourceTypeEntity = resourceTypeDAO.findByName(viewName);
      if (resourceTypeEntity == null) {
        resourceTypeEntity = resourceType;
        resourceTypeDAO.create(resourceTypeEntity);
      }

      for( ViewInstanceEntity instance : view.getInstances()) {
        instance.setResource(createViewInstanceResource(resourceType));
      }
      // ... merge the view
      persistedView = viewDAO.merge(view);
    }

    view.setResourceType(persistedView.getResourceType());
    view.setPermissions(persistedView.getPermissions());

    // make sure that each instance of the view in the db is reflected in the given view
    for (ViewInstanceEntity persistedInstance : persistedView.getInstances()){

      String             instanceName = persistedInstance.getName();
      ViewInstanceEntity instance     = view.getInstanceDefinition(instanceName);

      // if the persisted instance is not in the view ...
      if (instance == null) {
        if (persistedInstance.isXmlDriven()) {
          // this instance was persisted from an earlier view.xml but has been removed...
          // remove it from the db
          instanceDAO.remove(persistedInstance);
        } else {
          // this instance was not specified in the view.xml but was added through the API...
          // bind it to the view and add it to the registry
          instanceDAO.merge(persistedInstance);
          bindViewInstance(view, persistedInstance);
          instanceDefinitions.add(persistedInstance);
        }
      } else {
        syncViewInstance(instance, persistedInstance);
      }
    }
  }

  // sync the given view instance entity to the matching view instance entity in the registry
  private void syncViewInstance(ViewInstanceEntity instanceEntity) {
    String viewName     = instanceEntity.getViewDefinition().getViewName();
    String version      = instanceEntity.getViewDefinition().getVersion();
    String instanceName = instanceEntity.getInstanceName();

    ViewInstanceEntity registryEntry = getInstanceDefinition(viewName, version, instanceName);
    if (registryEntry != null) {
      syncViewInstance(registryEntry, instanceEntity);
    }
  }

  // sync a given view instance entity with another given view instance entity
  private void syncViewInstance(ViewInstanceEntity instance1, ViewInstanceEntity instance2) {
    instance1.setLabel(instance2.getLabel());
    instance1.setDescription(instance2.getDescription());
    instance1.setVisible(instance2.isVisible());
    instance1.setResource(instance2.getResource());
    instance1.setViewInstanceId(instance2.getViewInstanceId());
    instance1.setData(instance2.getData());
    instance1.setEntities(instance2.getEntities());
    instance1.setProperties(instance2.getProperties());
  }

  // create an admin resource to represent a view instance
  private ResourceEntity createViewInstanceResource(ResourceTypeEntity resourceTypeEntity) {
    ResourceEntity resourceEntity = new ResourceEntity();
    resourceEntity.setResourceType(resourceTypeEntity);
    resourceDAO.create(resourceEntity);
    return resourceEntity;
  }

  // notify the view identified by the given view name of the given event
  private void fireEvent(Event event, String viewName) {
    Set<Listener> listeners = this.listeners.get(viewName);

    if (listeners != null) {
      for (Listener listener : listeners) {
        listener.notify(event);
      }
    }
  }

  // check that the current user is authorized to access the given view instance resource
  private boolean checkAuthorization(ResourceEntity resourceEntity) {
    for (GrantedAuthority grantedAuthority : securityHelper.getCurrentAuthorities()) {
      if (grantedAuthority instanceof AmbariGrantedAuthority) {

        AmbariGrantedAuthority authority       = (AmbariGrantedAuthority) grantedAuthority;
        PrivilegeEntity        privilegeEntity = authority.getPrivilegeEntity();
        Integer                permissionId    = privilegeEntity.getPermission().getId();

        // admin has full access
        if (permissionId.equals(PermissionEntity.AMBARI_ADMIN_PERMISSION)) {
          return true;
        }
        if (resourceEntity != null) {
          // VIEW.USE for the given view instance resource.
          if (privilegeEntity.getResource().equals(resourceEntity)) {
            if (permissionId.equals(PermissionEntity.VIEW_USE_PERMISSION)) {
              return true;
            }
          }
        }
      }
    }
    // TODO : should we log this?
    return false;
  }

  // fire the onDeploy event.
  protected void onDeploy(ViewEntity definition) {
    View view = definition.getView();
    if (view != null) {
      view.onDeploy(definition);
    }
  }

  // remove a privilege entity.
  private void removePrivilegeEntity(PrivilegeEntity privilegeEntity) {

    PrincipalEntity principalEntity = privilegeEntity.getPrincipal();
    if (principalEntity != null) {
      principalEntity.removePrivilege(privilegeEntity);
    }

    privilegeDAO.remove(privilegeEntity);
  }

  // read the view archives.
  private void readViewArchives(boolean systemOnly, boolean useExecutor,
                                String viewNameRegExp, boolean removeUndeployed) {
    try {
      File viewDir = configuration.getViewsDir();

      String extractedArchivesPath = viewDir.getAbsolutePath() +
          File.separator + EXTRACTED_ARCHIVES_DIR;

      if (extractor.ensureExtractedArchiveDirectory(extractedArchivesPath)) {

        File[] files  = viewDir.listFiles();

        if (files != null) {

          Set<Runnable> extractionRunnables = new HashSet<Runnable>();

          final String serverVersion = ambariMetaInfo.get().getServerVersion();

          for (final File archiveFile : files) {
            if (!archiveFile.isDirectory()) {

              try {
                final ViewConfig viewConfig = archiveUtility.getViewConfigFromArchive(archiveFile);

                String commonName = viewConfig.getName();
                String version    = viewConfig.getVersion();
                String viewName   = ViewEntity.getViewName(commonName, version);

                if (!viewName.matches(viewNameRegExp)) {
                  continue;
                }

                final String extractedArchiveDirPath = extractedArchivesPath + File.separator + viewName;
                final File extractedArchiveDirFile = archiveUtility.getFile(extractedArchiveDirPath);

                final ViewEntity viewDefinition = new ViewEntity(viewConfig, configuration, extractedArchiveDirPath);

                boolean systemView = viewDefinition.isSystem();

                if (!systemOnly || systemView) {
                  // update the registry with the view
                  addDefinition(viewDefinition);

                  // always load system views up front
                  if (systemView || !useExecutor || extractedArchiveDirFile.exists()) {
                    // if the archive is already extracted then load the view now
                    readViewArchive(viewDefinition, archiveFile, extractedArchiveDirFile, serverVersion);
                  } else {
                    // if the archive needs to be extracted then create a runnable to do it
                    extractionRunnables.add(new Runnable() {
                      @Override
                      public void run() {
                        readViewArchive(viewDefinition, archiveFile, extractedArchiveDirFile, serverVersion);
                      }
                    });
                  }
                }
              } catch (Exception e) {
                String msg = "Caught exception reading view archive " + archiveFile.getAbsolutePath();
                LOG.error(msg, e);
              }
            }
          }

          if (useExecutor && extractionRunnables.size() > 0) {
            final ExecutorService executorService = getExecutorService(configuration);

            for (Runnable runnable : extractionRunnables) {
              // submit a new task for each archive that needs extraction
              executorService.submit(runnable);
            }
          }

          if (removeUndeployed) {
            removeUndeployedViews();
          }
        }
      } else {
        LOG.error("Could not create extracted view archive directory " + extractedArchivesPath + ".");
      }
    } catch (Exception e) {
      LOG.error("Caught exception reading view archives.", e);
    }
  }

  // read a view archive
  private void readViewArchive(ViewEntity viewDefinition,
                               File archiveFile,
                               File extractedArchiveDirFile,
                               String serverVersion) {

    setViewStatus(viewDefinition, ViewEntity.ViewStatus.DEPLOYING, "Deploying " + extractedArchiveDirFile + ".");

    String extractedArchiveDirPath = extractedArchiveDirFile.getAbsolutePath();

    try {
      // extract the archive and get the class loader
      ClassLoader cl = extractor.extractViewArchive(viewDefinition, archiveFile, extractedArchiveDirFile);

      ViewConfig viewConfig = archiveUtility.getViewConfigFromExtractedArchive(extractedArchiveDirPath,
          configuration.isViewValidationEnabled());

      viewDefinition.setConfiguration(viewConfig);

      if (checkViewVersions(viewDefinition, serverVersion)) {
        setupViewDefinition(viewDefinition, cl);

        Set<ViewInstanceEntity> instanceDefinitions = new HashSet<ViewInstanceEntity>();

        for (InstanceConfig instanceConfig : viewConfig.getInstances()) {
          ViewInstanceEntity instanceEntity = createViewInstanceDefinition(viewConfig, viewDefinition, instanceConfig);
          instanceEntity.setXmlDriven(true);
          instanceDefinitions.add(instanceEntity);
        }
        persistView(viewDefinition, instanceDefinitions);

        setViewStatus(viewDefinition, ViewEntity.ViewStatus.DEPLOYED, "Deployed " + extractedArchiveDirPath + ".");
      }
    } catch (Exception e) {
      String msg = "Caught exception loading view " + viewDefinition.getName();

      setViewStatus(viewDefinition, ViewEntity.ViewStatus.ERROR, msg + " : " + e.getMessage());
      LOG.error(msg, e);
    }
  }

  /**
   * Check the configured view max and min Ambari versions for the given view entity
   * against the given Ambari server version.
   *
   * @param view           the view
   * @param serverVersion  the server version
   *
   * @return true if the given server version >= min version && <= max version for the given view
   */
  protected boolean checkViewVersions(ViewEntity view, String serverVersion) {
    ViewConfig config = view.getConfiguration();

    return checkViewVersion(view, config.getMinAmbariVersion(), serverVersion, "minimum", 1, "less than") &&
           checkViewVersion(view, config.getMaxAmbariVersion(), serverVersion, "maximum", -1, "greater than");

  }

  // check the given version against the actual Ambari server version
  private boolean checkViewVersion(ViewEntity view, String version, String serverVersion, String label,
                                   int errValue, String errMsg) {

    if (version != null && !version.isEmpty()) {

      // make sure that the given version is a valid version string
      if (!version.matches(VIEW_AMBARI_VERSION_REGEXP)) {
        String msg = "The configured " + label + " Ambari version " + version + " for view " +
            view.getName() + " is not valid.";

        setViewStatus(view, ViewEntity.ViewStatus.ERROR, msg);
        LOG.error(msg);
        return false;
      }

      int index = version.indexOf('*');

      int compVal = index == -1 ? VersionUtils.compareVersions(version, serverVersion) :
                    index > 0 ? VersionUtils.compareVersions(version.substring(0, index), serverVersion, index) : 0;

      if (compVal == errValue) {
        String msg = "The Ambari server version " + serverVersion + " is " + errMsg + " the configured " + label +
            " Ambari version " + version + " for view " + view.getName();

        setViewStatus(view, ViewEntity.ViewStatus.ERROR, msg);
        LOG.error(msg);
        return false;
      }
    }
    return true;
  }

  // persist the given view and its instances
  @Transactional
  private void persistView(ViewEntity viewDefinition, Set<ViewInstanceEntity> instanceDefinitions) throws Exception {
    // ensure that the view entity matches the db
    syncView(viewDefinition, instanceDefinitions);

    onDeploy(viewDefinition);

    // update the registry with the view instances
    for (ViewInstanceEntity instanceEntity : instanceDefinitions) {
      addInstanceDefinition(viewDefinition, instanceEntity);
      handlerList.addViewInstance(instanceEntity);
    }
  }

  // extract the view archive for the given path.
  protected static boolean extractViewArchive(String archivePath,
                                            ViewExtractor extractor,
                                            ViewArchiveUtility archiveUtility,
                                            Configuration configuration,
                                            boolean systemOnly ) throws Exception {

    File viewDir = configuration.getViewsDir();

    String extractedArchivesPath = viewDir.getAbsolutePath() +
        File.separator + EXTRACTED_ARCHIVES_DIR;

    if (extractor.ensureExtractedArchiveDirectory(extractedArchivesPath)) {

      File archiveFile = archiveUtility.getFile(archivePath);

      ViewConfig viewConfig = archiveUtility.getViewConfigFromArchive(archiveFile);

      String commonName = viewConfig.getName();
      String version    = viewConfig.getVersion();
      String viewName   = ViewEntity.getViewName(commonName, version);

      String extractedArchiveDirPath = extractedArchivesPath + File.separator + viewName;
      File   extractedArchiveDirFile = archiveUtility.getFile(extractedArchiveDirPath);

      if (!extractedArchiveDirFile.exists()) {
        ViewEntity viewDefinition = new ViewEntity(viewConfig, configuration, extractedArchiveDirPath);

        if (!systemOnly || viewDefinition.isSystem()) {
          extractor.extractViewArchive(viewDefinition, archiveFile, extractedArchiveDirFile);
          return true;
        }
      }
    }
    return false;
  }

  // set the status of the given view.
  private void setViewStatus(ViewEntity viewDefinition, ViewEntity.ViewStatus status, String statusDetail) {
    viewDefinition.setStatus(status);
    viewDefinition.setStatusDetail(statusDetail);
  }

  // Get the view extraction thread pool
  private static synchronized ExecutorService getExecutorService(Configuration configuration) {
    if (executorService == null) {
      LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();

      ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
          configuration.getViewExtractionThreadPoolCoreSize(),
          configuration.getViewExtractionThreadPoolMaxSize(),
          configuration.getViewExtractionThreadPoolTimeout(),
          TimeUnit.MILLISECONDS,
          queue);

      threadPoolExecutor.allowCoreThreadTimeOut(true);
      executorService = threadPoolExecutor;
    }
    return executorService;
  }

  /**
   * Factory method to create a view URL stream provider.
   *
   * @param viewContext  the view context
   *
   * @return a new view URL stream provider
   */
  protected ViewURLStreamProvider createURLStreamProvider(ViewContext viewContext) {
    ComponentSSLConfiguration sslConfiguration = ComponentSSLConfiguration.instance();
    org.apache.ambari.server.controller.internal.URLStreamProvider streamProvider =
        new org.apache.ambari.server.controller.internal.URLStreamProvider(
            configuration.getRequestConnectTimeout(),
            configuration.getRequestReadTimeout(),
            sslConfiguration.getTruststorePath(),
            sslConfiguration.getTruststorePassword(),
            sslConfiguration.getTruststoreType());
    return new ViewURLStreamProvider(viewContext, streamProvider);
  }

  /**
   * Factory method to create a view Ambari stream provider.
   *
   * @return a new view Ambari stream provider
   */
  protected ViewAmbariStreamProvider createAmbariStreamProvider() {
    ComponentSSLConfiguration sslConfiguration = ComponentSSLConfiguration.instance();
    org.apache.ambari.server.controller.internal.URLStreamProvider streamProvider =
        new org.apache.ambari.server.controller.internal.URLStreamProvider(
            DEFAULT_REQUEST_CONNECT_TIMEOUT,
            DEFAULT_REQUEST_READ_TIMEOUT,
            sslConfiguration.getTruststorePath(),
            sslConfiguration.getTruststorePassword(),
            sslConfiguration.getTruststoreType());
    return new ViewAmbariStreamProvider(streamProvider, ambariSessionManager, AmbariServer.getController());
  }
}
