/*
 * 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 static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.capture;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.createNiceMock;
import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.reset;
import static org.easymock.EasyMock.verify;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.util.ArrayList;
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.Properties;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

import javax.xml.bind.JAXBException;

import org.apache.ambari.server.api.resources.SubResourceDefinition;
import org.apache.ambari.server.api.services.AmbariMetaInfo;
import org.apache.ambari.server.configuration.Configuration;
import org.apache.ambari.server.controller.spi.Resource;
import org.apache.ambari.server.controller.spi.ResourceProvider;
import org.apache.ambari.server.events.ServiceInstalledEvent;
import org.apache.ambari.server.events.publishers.AmbariEventPublisher;
import org.apache.ambari.server.orm.dao.MemberDAO;
import org.apache.ambari.server.orm.dao.PermissionDAO;
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.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.ViewEntity;
import org.apache.ambari.server.orm.entities.ViewEntityEntity;
import org.apache.ambari.server.orm.entities.ViewEntityTest;
import org.apache.ambari.server.orm.entities.ViewInstanceDataEntity;
import org.apache.ambari.server.orm.entities.ViewInstanceEntity;
import org.apache.ambari.server.orm.entities.ViewInstanceEntityTest;
import org.apache.ambari.server.security.SecurityHelper;
import org.apache.ambari.server.security.TestAuthenticationFactory;
import org.apache.ambari.server.security.authorization.ResourceType;
import org.apache.ambari.server.state.Cluster;
import org.apache.ambari.server.state.Clusters;
import org.apache.ambari.server.state.Service;
import org.apache.ambari.server.state.StackId;
import org.apache.ambari.server.state.stack.OsFamily;
import org.apache.ambari.server.view.configuration.InstanceConfig;
import org.apache.ambari.server.view.configuration.InstanceConfigTest;
import org.apache.ambari.server.view.configuration.PropertyConfig;
import org.apache.ambari.server.view.configuration.ViewConfig;
import org.apache.ambari.server.view.configuration.ViewConfigTest;
import org.apache.ambari.server.view.events.EventImpl;
import org.apache.ambari.server.view.events.EventImplTest;
import org.apache.ambari.server.view.validation.ValidationException;
import org.apache.ambari.view.ViewDefinition;
import org.apache.ambari.view.events.Event;
import org.apache.ambari.view.events.Listener;
import org.apache.ambari.view.validation.ValidationResult;
import org.apache.ambari.view.validation.Validator;
import org.easymock.Capture;
import org.easymock.CaptureType;
import org.easymock.EasyMock;
import org.easymock.IAnswer;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.security.core.context.SecurityContextHolder;

import com.google.inject.Provider;

/**
 * ViewRegistry tests.
 */
public class ViewRegistryTest {

  private static final String VIEW_XML_1 = "<view>\n" +
      "    <name>MY_VIEW</name>\n" +
      "    <label>My View!</label>\n" +
      "    <version>1.0.0</version>\n" +
      "</view>";

  private static final String VIEW_XML_2 = "<view>\n" +
      "    <name>MY_VIEW</name>\n" +
      "    <label>My View!</label>\n" +
      "    <version>2.0.0</version>\n" +
      "</view>";

  private static final String XML_VALID_INSTANCE = "<view>\n" +
      "    <name>MY_VIEW</name>\n" +
      "    <label>My View!</label>\n" +
      "    <version>1.0.0</version>\n" +
      "    <parameter>\n" +
      "        <name>p1</name>\n" +
      "        <description>Parameter 1.</description>\n" +
      "        <required>true</required>\n" +
      "    </parameter>\n" +
      "    <parameter>\n" +
      "        <name>p2</name>\n" +
      "        <description>Parameter 2.</description>\n" +
      "        <masked>true</masked>" +
      "        <required>false</required>\n" +
      "    </parameter>\n" +
      "    <instance>\n" +
      "        <name>INSTANCE1</name>\n" +
      "        <label>My Instance 1!</label>\n" +
      "        <property>\n" +
      "            <key>p1</key>\n" +
      "            <value>v1-1</value>\n" +
      "        </property>\n" +
      "        <property>\n" +
      "            <key>p2</key>\n" +
      "            <value>v2-1</value>\n" +
      "        </property>\n" +
      "    </instance>\n" +
      "</view>";

  private static final String XML_INVALID_INSTANCE = "<view>\n" +
      "    <name>MY_VIEW</name>\n" +
      "    <label>My View!</label>\n" +
      "    <version>1.0.0</version>\n" +
      "    <parameter>\n" +
      "        <name>p1</name>\n" +
      "        <description>Parameter 1.</description>\n" +
      "        <required>true</required>\n" +
      "    </parameter>\n" +
      "    <parameter>\n" +
      "        <name>p2</name>\n" +
      "        <description>Parameter 2.</description>\n" +
      "        <required>false</required>\n" +
      "    </parameter>\n" +
      "    <instance>\n" +
      "        <name>INSTANCE1</name>\n" +
      "        <label>My Instance 1!</label>\n" +
      "    </instance>\n" +
      "</view>";

  private static final String AUTO_VIEW_XML = "<view>\n" +
      "    <name>MY_VIEW</name>\n" +
      "    <label>My View!</label>\n" +
      "    <version>1.0.0</version>\n" +
      "    <auto-instance>\n" +
      "        <name>AUTO-INSTANCE</name>\n" +
      "        <stack-id>HDP-2.0</stack-id>\n" +
      "        <services><service>HIVE</service><service>HDFS</service></services>\n" +
      "    </auto-instance>\n" +
      "</view>";

  private static final String AUTO_VIEW_WILD_STACK_XML = "<view>\n" +
      "    <name>MY_VIEW</name>\n" +
      "    <label>My View!</label>\n" +
      "    <version>1.0.0</version>\n" +
      "    <auto-instance>\n" +
      "        <name>AUTO-INSTANCE</name>\n" +
      "        <stack-id>HDP-2.*</stack-id>\n" +
      "        <services><service>HIVE</service><service>HDFS</service></services>\n" +
      "    </auto-instance>\n" +
      "</view>";

  private static final String AUTO_VIEW_WILD_ALL_STACKS_XML = "<view>\n" +
    "    <name>MY_VIEW</name>\n" +
    "    <label>My View!</label>\n" +
    "    <version>1.0.0</version>\n" +
    "    <auto-instance>\n" +
    "        <name>AUTO-INSTANCE</name>\n" +
    "        <stack-id>*</stack-id>\n" +
    "        <services><service>HIVE</service><service>HDFS</service></services>\n" +
    "    </auto-instance>\n" +
    "</view>";

  private static final String AUTO_VIEW_BAD_STACK_XML = "<view>\n" +
      "    <name>MY_VIEW</name>\n" +
      "    <label>My View!</label>\n" +
      "    <version>1.0.0</version>\n" +
      "    <auto-instance>\n" +
      "        <name>AUTO-INSTANCE</name>\n" +
      "        <stack-id>HDP-2.5</stack-id>\n" +
      "        <services><service>HIVE</service><service>HDFS</service></services>\n" +
      "    </auto-instance>\n" +
      "</view>";

  private static final String EXPECTED_HDP_2_0_STACK_NAME = "HDP-2.0";

  // registry mocks
  private static final ViewDAO viewDAO = createMock(ViewDAO.class);
  private static final ViewInstanceDAO viewInstanceDAO = createNiceMock(ViewInstanceDAO.class);
  private static final ViewInstanceOperationHandler viewInstanceOperationHandler = createNiceMock(ViewInstanceOperationHandler.class);
  private static final UserDAO userDAO = createNiceMock(UserDAO.class);
  private static final MemberDAO memberDAO = createNiceMock(MemberDAO.class);
  private static final PrivilegeDAO privilegeDAO = createNiceMock(PrivilegeDAO.class);
  private static final PermissionDAO permissionDAO = createNiceMock(PermissionDAO.class);
  private static final ResourceDAO resourceDAO = createNiceMock(ResourceDAO.class);
  private static final ResourceTypeDAO resourceTypeDAO = createNiceMock(ResourceTypeDAO.class);
  private static final SecurityHelper securityHelper = createNiceMock(SecurityHelper.class);
  private static final Configuration configuration = createNiceMock(Configuration.class);
  private static final ViewInstanceHandlerList handlerList = createNiceMock(ViewInstanceHandlerList.class);
  private static final AmbariMetaInfo ambariMetaInfo = createNiceMock(AmbariMetaInfo.class);
  private static final Clusters clusters = createNiceMock(Clusters.class);



  @Before
  public void resetGlobalMocks() {
    ViewRegistry.initInstance(getRegistry(viewInstanceOperationHandler, viewDAO, viewInstanceDAO, userDAO, memberDAO, privilegeDAO,
        permissionDAO, resourceDAO, resourceTypeDAO, securityHelper, handlerList, null, null, ambariMetaInfo, clusters));

    reset(viewInstanceOperationHandler, viewDAO, resourceDAO, viewInstanceDAO, userDAO, memberDAO,
        privilegeDAO, resourceTypeDAO, securityHelper, configuration, handlerList, ambariMetaInfo,
        clusters);
  }

  @After
  public void clearAuthentication() {
    SecurityContextHolder.getContext().setAuthentication(null);
  }

  @Test
  public void testReadViewArchives() throws Exception {
    testReadViewArchives(false, false, false);
  }

  @Test
  public void testReadViewArchives_removeUndeployed() throws Exception {
    testReadViewArchives(false, true, false);
  }

  @Test
  public void testReadViewArchives_badArchive() throws Exception {
    testReadViewArchives(true, false, false);
  }


  @Ignore("this will get refactored when divorced from the stack")
  public void testReadViewArchives_viewAutoInstanceCreation() throws Exception {
    testReadViewArchives(false, false, true);
  }

  private void testReadViewArchives(boolean badArchive, boolean removeUndeployed, boolean checkAutoInstanceCreation) throws Exception {

    File viewDir = createNiceMock(File.class);
    File extractedArchiveDir = createNiceMock(File.class);
    File viewArchive = createNiceMock(File.class);
    File archiveDir = createNiceMock(File.class);
    File entryFile  = createNiceMock(File.class);
    File classesDir = createNiceMock(File.class);
    File libDir = createNiceMock(File.class);
    File metaInfDir = createNiceMock(File.class);
    File fileEntry = createNiceMock(File.class);

    JarInputStream viewJarFile = createNiceMock(JarInputStream.class);
    JarEntry jarEntry = createNiceMock(JarEntry.class);
    FileOutputStream fos = createMock(FileOutputStream.class);

    ResourceTypeEntity resourceTypeEntity = new ResourceTypeEntity();
    resourceTypeEntity.setId(10);
    resourceTypeEntity.setName("MY_VIEW{1.0.0}");

    ViewEntity viewDefinition = ViewEntityTest.getViewEntity();
    viewDefinition.setResourceType(resourceTypeEntity);

    Set<ViewInstanceEntity> viewInstanceEntities = ViewInstanceEntityTest.getViewInstanceEntities(viewDefinition);

    for (ViewInstanceEntity viewInstanceEntity : viewInstanceEntities) {
      viewInstanceEntity.putInstanceData("p1", "v1");

      Collection<ViewEntityEntity> entities = new HashSet<>();
      ViewEntityEntity viewEntityEntity = new ViewEntityEntity();
      viewEntityEntity.setId(99L);
      viewEntityEntity.setIdProperty("id");
      viewEntityEntity.setViewName("MY_VIEW{1.0.0}");
      viewEntityEntity.setClassName("class");
      viewEntityEntity.setViewInstanceName(viewInstanceEntity.getName());
      viewEntityEntity.setViewInstance(viewInstanceEntity);
      entities.add(viewEntityEntity);

      viewInstanceEntity.setEntities(entities);
    }
    viewDefinition.setInstances(viewInstanceEntities);

    Map<File, ViewConfig> viewConfigs =
        Collections.singletonMap(viewArchive, viewDefinition.getConfiguration());

    long resourceId = 99L;
    for (ViewInstanceEntity viewInstanceEntity : viewInstanceEntities) {
      ResourceEntity resourceEntity = new ResourceEntity();
      resourceEntity.setId(resourceId);
      resourceEntity.setResourceType(resourceTypeEntity);
      viewInstanceEntity.setResource(resourceEntity);
    }

    Map<String, File> files = new HashMap<>();
    if (System.getProperty("os.name").contains("Windows")) {
      files.put("\\var\\lib\\ambari-server\\resources\\views\\work", extractedArchiveDir);
      files.put("\\var\\lib\\ambari-server\\resources\\views\\work\\MY_VIEW{1.0.0}", archiveDir);
      files.put("\\var\\lib\\ambari-server\\resources\\views\\work\\MY_VIEW{1.0.0}\\view.xml", entryFile);
      files.put("\\var\\lib\\ambari-server\\resources\\views\\work\\MY_VIEW{1.0.0}\\WEB-INF/classes", classesDir);
      files.put("\\var\\lib\\ambari-server\\resources\\views\\work\\MY_VIEW{1.0.0}\\WEB-INF/lib", libDir);
      files.put("\\var\\lib\\ambari-server\\resources\\views\\work\\MY_VIEW{1.0.0}\\META-INF", metaInfDir);
    }
    else {
      files.put("/var/lib/ambari-server/resources/views/work", extractedArchiveDir);
      files.put("/var/lib/ambari-server/resources/views/work/MY_VIEW{1.0.0}", archiveDir);
      files.put("/var/lib/ambari-server/resources/views/work/MY_VIEW{1.0.0}/view.xml", entryFile);
      files.put("/var/lib/ambari-server/resources/views/work/MY_VIEW{1.0.0}/WEB-INF/classes", classesDir);
      files.put("/var/lib/ambari-server/resources/views/work/MY_VIEW{1.0.0}/WEB-INF/lib", libDir);
      files.put("/var/lib/ambari-server/resources/views/work/MY_VIEW{1.0.0}/META-INF", metaInfDir);
    }

    Map<File, FileOutputStream> outputStreams = new HashMap<>();
    outputStreams.put(entryFile, fos);

    Map<File, JarInputStream> jarFiles = new HashMap<>();
    jarFiles.put(viewArchive, viewJarFile);

    // set expectations
    expect(configuration.getViewsDir()).andReturn(viewDir).anyTimes();
    if (System.getProperty("os.name").contains("Windows")) {
      expect(viewDir.getAbsolutePath()).andReturn("\\var\\lib\\ambari-server\\resources\\views").anyTimes();
    }
    else {
      expect(viewDir.getAbsolutePath()).andReturn("/var/lib/ambari-server/resources/views").anyTimes();
    }

    expect(configuration.getViewExtractionThreadPoolCoreSize()).andReturn(2).anyTimes();
    expect(configuration.getViewExtractionThreadPoolMaxSize()).andReturn(3).anyTimes();
    expect(configuration.getViewExtractionThreadPoolTimeout()).andReturn(10000L).anyTimes();
    expect(configuration.extractViewsAfterClusterConfig()).andReturn(Boolean.FALSE).anyTimes();

    expect(viewDir.listFiles()).andReturn(new File[]{viewArchive});

    expect(viewArchive.isDirectory()).andReturn(false);
    if (System.getProperty("os.name").contains("Windows")) {
      expect(viewArchive.getAbsolutePath()).andReturn("\\var\\lib\\ambari-server\\resources\\views\\work\\MY_VIEW{1.0.0}").anyTimes();
    }
    else {
      expect(viewArchive.getAbsolutePath()).andReturn("/var/lib/ambari-server/resources/views/work/MY_VIEW{1.0.0}").anyTimes();
    }

    expect(archiveDir.exists()).andReturn(false).anyTimes();
    if (System.getProperty("os.name").contains("Windows")) {
      expect(archiveDir.getAbsolutePath()).andReturn("\\var\\lib\\ambari-server\\resources\\views\\work\\MY_VIEW{1.0.0}").anyTimes();
    }
    else {
      expect(archiveDir.getAbsolutePath()).andReturn("/var/lib/ambari-server/resources/views/work/MY_VIEW{1.0.0}").anyTimes();
    }
    expect(archiveDir.mkdir()).andReturn(true).anyTimes();
    expect(archiveDir.toURI()).andReturn(new URI("file:./")).anyTimes();

    expect(metaInfDir.mkdir()).andReturn(true).anyTimes();

    if (!badArchive) {
      expect(viewJarFile.getNextJarEntry()).andReturn(jarEntry);
      expect(viewJarFile.getNextJarEntry()).andReturn(null);

      expect(jarEntry.getName()).andReturn("view.xml");
      expect(jarEntry.isDirectory()).andReturn(false);

      expect(viewJarFile.read(anyObject(byte[].class))).andReturn(10);
      expect(viewJarFile.read(anyObject(byte[].class))).andReturn(-1);
      fos.write(anyObject(byte[].class), eq(0), eq(10));

      fos.flush();
      fos.close();
      viewJarFile.closeEntry();
      viewJarFile.close();

      expect(viewDAO.findByName("MY_VIEW{1.0.0}")).andReturn(viewDefinition);
    }

    expect(extractedArchiveDir.exists()).andReturn(false).anyTimes();
    expect(extractedArchiveDir.mkdir()).andReturn(true).anyTimes();

    expect(classesDir.exists()).andReturn(true).anyTimes();
    expect(classesDir.toURI()).andReturn(new URI("file:./")).anyTimes();

    expect(libDir.exists()).andReturn(true).anyTimes();

    expect(libDir.listFiles()).andReturn(new File[]{fileEntry}).anyTimes();
    expect(fileEntry.toURI()).andReturn(new URI("file:./")).anyTimes();

    expect(configuration.isViewRemoveUndeployedEnabled()).andReturn(removeUndeployed).anyTimes();

    Cluster cluster = createNiceMock(Cluster.class);
    Service service = createNiceMock(Service.class);
    ViewInstanceEntity viewAutoInstanceEntity = createNiceMock(ViewInstanceEntity.class);
    Capture<ViewInstanceEntity> viewAutoInstanceCapture = EasyMock.newCapture();

    ViewInstanceDataEntity autoInstanceDataEntity = createNiceMock(ViewInstanceDataEntity.class);
    expect(autoInstanceDataEntity.getName()).andReturn("p1").anyTimes();
    expect(autoInstanceDataEntity.getUser()).andReturn(" ").anyTimes();

    Map<String, Service> serviceMap = new HashMap<>();
    serviceMap.put("HDFS", service);
    serviceMap.put("HIVE", service);


    StackId stackId = new StackId("HDP-2.0");

    if(checkAutoInstanceCreation) {
      Map<String, Cluster> allClusters = new HashMap<>();
      expect(cluster.getClusterName()).andReturn("c1").anyTimes();
      expect(cluster.getCurrentStackVersion()).andReturn(stackId).anyTimes();
      expect(cluster.getServices()).andReturn(serviceMap).anyTimes();
      allClusters.put("c1", cluster);
      expect(clusters.getClusters()).andReturn(allClusters);

      expect(viewInstanceDAO.merge(capture(viewAutoInstanceCapture))).andReturn(viewAutoInstanceEntity).anyTimes();
      expect(viewInstanceDAO.findByName("MY_VIEW{1.0.0}", "AUTO-INSTANCE")).andReturn(viewAutoInstanceEntity).anyTimes();
      expect(viewAutoInstanceEntity.getInstanceData("p1")).andReturn(autoInstanceDataEntity).anyTimes();
    } else {
      expect(clusters.getClusters()).andReturn(new HashMap<>());
    }

    if (removeUndeployed) {
      expect(viewDAO.findAll()).andReturn(Collections.emptyList());
    }

    // replay mocks
    replay(configuration, viewDir, extractedArchiveDir, viewArchive, archiveDir, entryFile, classesDir,
        libDir, metaInfDir, fileEntry, viewJarFile, jarEntry, fos, resourceDAO, viewDAO, viewInstanceDAO, clusters,
      cluster, viewAutoInstanceEntity);

    TestViewArchiveUtility archiveUtility =
        new TestViewArchiveUtility(viewConfigs, files, outputStreams, jarFiles, badArchive);

    ViewRegistry registry = getRegistry(viewInstanceOperationHandler, viewDAO, viewInstanceDAO, userDAO, memberDAO, privilegeDAO, permissionDAO,
        resourceDAO, resourceTypeDAO, securityHelper, handlerList, null, archiveUtility, ambariMetaInfo, clusters);

    registry.readViewArchives();

    ViewEntity view = null;

    // Wait for the view load to complete.
    long timeout = System.currentTimeMillis() + 10000L;
    while (!archiveUtility.isDeploymentFailed() && (view == null || !view.getStatus().equals(ViewDefinition.ViewStatus.DEPLOYED))&&
        System.currentTimeMillis() < timeout) {
      view = registry.getDefinition("MY_VIEW", "1.0.0");
    }

    int instanceDefinitionSize = checkAutoInstanceCreation ? 3: 2;

    if (badArchive) {
      Assert.assertNull(view);
      Assert.assertTrue(archiveUtility.isDeploymentFailed());
    } else {
      Assert.assertNotNull(view);
      Assert.assertEquals(ViewDefinition.ViewStatus.DEPLOYED, view.getStatus());

      Collection<ViewInstanceEntity> instanceDefinitions = registry.getInstanceDefinitions(view);
      ArrayList<ViewInstanceEntity> filteredInstanceDefinition = new ArrayList<>();
      Assert.assertEquals(instanceDefinitionSize, instanceDefinitions.size());

      if(checkAutoInstanceCreation) {
        Assert.assertEquals(viewAutoInstanceCapture.getValue(), registry.getInstanceDefinition("MY_VIEW", "1.0.0", "AUTO-INSTANCE"));
      }

      //Filter out AutoInstance view Entity
      for(ViewInstanceEntity entity : instanceDefinitions) {
        if(!entity.getName().equals("AUTO-INSTANCE")) {
          filteredInstanceDefinition.add(entity);
        }
      }

      for (ViewInstanceEntity viewInstanceEntity : filteredInstanceDefinition) {
        Assert.assertEquals("v1", viewInstanceEntity.getInstanceData("p1").getValue());

        Collection<ViewEntityEntity> entities = viewInstanceEntity.getEntities();
        Assert.assertEquals(1, entities.size());
        ViewEntityEntity viewEntityEntity = entities.iterator().next();
        Assert.assertEquals(99L, (long) viewEntityEntity.getId());
        Assert.assertEquals(viewInstanceEntity.getName(), viewEntityEntity.getViewInstanceName());
      }
    }



    // verify mocks
    verify(configuration, viewDir, extractedArchiveDir, viewArchive, archiveDir, entryFile, classesDir,
        libDir, metaInfDir, fileEntry, viewJarFile, jarEntry, fos, resourceDAO, viewDAO, viewInstanceDAO);
  }

  @Test
  public void testReadViewArchives_exception() throws Exception {

    File viewDir = createNiceMock(File.class);
    File extractedArchiveDir = createNiceMock(File.class);
    File viewArchive = createNiceMock(File.class);
    File archiveDir = createNiceMock(File.class);
    File entryFile  = createNiceMock(File.class);
    File classesDir = createNiceMock(File.class);
    File libDir = createNiceMock(File.class);
    File metaInfDir = createNiceMock(File.class);
    File fileEntry = createNiceMock(File.class);

    JarInputStream viewJarFile = createNiceMock(JarInputStream.class);
    JarEntry jarEntry = createNiceMock(JarEntry.class);
    FileOutputStream fos = createMock(FileOutputStream.class);

    ResourceTypeEntity resourceTypeEntity = new ResourceTypeEntity();
    resourceTypeEntity.setId(10);
    resourceTypeEntity.setName("MY_VIEW{1.0.0}");

    ViewEntity viewDefinition = ViewEntityTest.getViewEntity();
    viewDefinition.setResourceType(resourceTypeEntity);

    Set<ViewInstanceEntity> viewInstanceEntities = ViewInstanceEntityTest.getViewInstanceEntities(viewDefinition);
    viewDefinition.setInstances(viewInstanceEntities);

    Map<File, ViewConfig> viewConfigs =
        Collections.singletonMap(viewArchive, viewDefinition.getConfiguration());

    long resourceId = 99L;
    for (ViewInstanceEntity viewInstanceEntity : viewInstanceEntities) {
      ResourceEntity resourceEntity = new ResourceEntity();
      resourceEntity.setId(resourceId);
      resourceEntity.setResourceType(resourceTypeEntity);
      viewInstanceEntity.setResource(resourceEntity);
    }

    Map<String, File> files = new HashMap<>();

    if (System.getProperty("os.name").contains("Windows")) {
      files.put("\\var\\lib\\ambari-server\\resources\\views\\work", extractedArchiveDir);
      files.put("\\var\\lib\\ambari-server\\resources\\views\\work\\MY_VIEW{1.0.0}", archiveDir);
      files.put("\\var\\lib\\ambari-server\\resources\\views\\work\\MY_VIEW{1.0.0}\\view.xml", entryFile);
      files.put("\\var\\lib\\ambari-server\\resources\\views\\work\\MY_VIEW{1.0.0}\\WEB-INF/classes", classesDir);
      files.put("\\var\\lib\\ambari-server\\resources\\views\\work\\MY_VIEW{1.0.0}\\WEB-INF/lib", libDir);
      files.put("\\var\\lib\\ambari-server\\resources\\views\\work\\MY_VIEW{1.0.0}\\META-INF", metaInfDir);
    }
    else {
      files.put("/var/lib/ambari-server/resources/views/work", extractedArchiveDir);
      files.put("/var/lib/ambari-server/resources/views/work/MY_VIEW{1.0.0}", archiveDir);
      files.put("/var/lib/ambari-server/resources/views/work/MY_VIEW{1.0.0}/view.xml", entryFile);
      files.put("/var/lib/ambari-server/resources/views/work/MY_VIEW{1.0.0}/WEB-INF/classes", classesDir);
      files.put("/var/lib/ambari-server/resources/views/work/MY_VIEW{1.0.0}/WEB-INF/lib", libDir);
      files.put("/var/lib/ambari-server/resources/views/work/MY_VIEW{1.0.0}/META-INF", metaInfDir);
    }

    Map<File, FileOutputStream> outputStreams = new HashMap<>();
    outputStreams.put(entryFile, fos);

    Map<File, JarInputStream> jarFiles = new HashMap<>();
    jarFiles.put(viewArchive, viewJarFile);

    // set expectations
    expect(configuration.getViewsDir()).andReturn(viewDir).anyTimes();
    if (System.getProperty("os.name").contains("Windows")) {
      expect(viewDir.getAbsolutePath()).andReturn("\\var\\lib\\ambari-server\\resources\\views").anyTimes();
    }
    else {
      expect(viewDir.getAbsolutePath()).andReturn("/var/lib/ambari-server/resources/views").anyTimes();
    }

    expect(configuration.getViewExtractionThreadPoolCoreSize()).andReturn(2).anyTimes();
    expect(configuration.getViewExtractionThreadPoolMaxSize()).andReturn(3).anyTimes();
    expect(configuration.getViewExtractionThreadPoolTimeout()).andReturn(10000L).anyTimes();
    expect(configuration.extractViewsAfterClusterConfig()).andReturn(Boolean.FALSE).anyTimes();

    expect(viewDir.listFiles()).andReturn(new File[]{viewArchive}).anyTimes();

    expect(viewArchive.isDirectory()).andReturn(false);
    if (System.getProperty("os.name").contains("Windows")) {
      expect(viewArchive.getAbsolutePath()).andReturn("\\var\\lib\\ambari-server\\resources\\views\\work\\MY_VIEW{1.0.0}").anyTimes();
    }
    else {
      expect(viewArchive.getAbsolutePath()).andReturn("/var/lib/ambari-server/resources/views/work/MY_VIEW{1.0.0}").anyTimes();
    }

    expect(archiveDir.exists()).andReturn(false);
    if (System.getProperty("os.name").contains("Windows")) {
      expect(archiveDir.getAbsolutePath()).andReturn("\\var\\lib\\ambari-server\\resources\\views\\work\\MY_VIEW{1.0.0}").anyTimes();
    }
    else {
      expect(archiveDir.getAbsolutePath()).andReturn("/var/lib/ambari-server/resources/views/work/MY_VIEW{1.0.0}").anyTimes();
    }
    expect(archiveDir.mkdir()).andReturn(true);
    expect(archiveDir.toURI()).andReturn(new URI("file:./"));

    expect(metaInfDir.mkdir()).andReturn(true);

    expect(viewJarFile.getNextJarEntry()).andReturn(jarEntry);
    expect(viewJarFile.getNextJarEntry()).andReturn(null);

    expect(jarEntry.getName()).andReturn("view.xml");
    expect(jarEntry.isDirectory()).andReturn(false);

    expect(viewJarFile.read(anyObject(byte[].class))).andReturn(10);
    expect(viewJarFile.read(anyObject(byte[].class))).andReturn(-1);
    fos.write(anyObject(byte[].class), eq(0), eq(10));

    fos.flush();
    fos.close();
    viewJarFile.closeEntry();
    viewJarFile.close();

    expect(extractedArchiveDir.exists()).andReturn(false);
    expect(extractedArchiveDir.mkdir()).andReturn(true);

    expect(classesDir.exists()).andReturn(true);
    expect(classesDir.toURI()).andReturn(new URI("file:./"));

    expect(libDir.exists()).andReturn(true);

    expect(libDir.listFiles()).andReturn(new File[]{fileEntry});
    expect(fileEntry.toURI()).andReturn(new URI("file:./"));

    expect(viewDAO.findByName("MY_VIEW{1.0.0}")).andThrow(new IllegalArgumentException("Expected exception."));

    // replay mocks
    replay(configuration, viewDir, extractedArchiveDir, viewArchive, archiveDir, entryFile, classesDir,
        libDir, metaInfDir, fileEntry, viewJarFile, jarEntry, fos, viewDAO);

    TestViewArchiveUtility archiveUtility = new TestViewArchiveUtility(viewConfigs, files, outputStreams, jarFiles, false);

    ViewRegistry registry = getRegistry(viewDAO, viewInstanceDAO, userDAO, memberDAO, privilegeDAO, permissionDAO,
        resourceDAO, resourceTypeDAO, securityHelper, handlerList, null, archiveUtility, ambariMetaInfo);

    registry.readViewArchives();

    ViewEntity view = null;

    // Wait for the view load to complete.
    long timeout = System.currentTimeMillis() + 10000L;
    while ((view == null || !view.getStatus().equals(ViewDefinition.ViewStatus.ERROR))&&
        System.currentTimeMillis() < timeout) {
      view = registry.getDefinition("MY_VIEW", "1.0.0");
    }

    Assert.assertNotNull(view);
    Assert.assertEquals(ViewDefinition.ViewStatus.ERROR, view.getStatus());

    // verify mocks
    verify(configuration, viewDir, extractedArchiveDir, viewArchive, archiveDir, entryFile, classesDir,
        libDir, metaInfDir, fileEntry, viewJarFile, jarEntry, fos, viewDAO);
  }

  @Test
  public void testListener() throws Exception {
    ViewRegistry registry = ViewRegistry.getInstance();

    TestListener listener = new TestListener();
    registry.registerListener(listener, "MY_VIEW", "1.0.0");

    EventImpl event = EventImplTest.getEvent("MyEvent", Collections.emptyMap(), VIEW_XML_1);

    registry.fireEvent(event);

    Assert.assertEquals(event, listener.getLastEvent());

    listener.clear();

    // fire an event for a different view
    event = EventImplTest.getEvent("MyEvent", Collections.emptyMap(), VIEW_XML_2);

    registry.fireEvent(event);

    Assert.assertNull(listener.getLastEvent());

    // un-register the listener
    registry.unregisterListener(listener, "MY_VIEW", "1.0.0");

    event = EventImplTest.getEvent("MyEvent", Collections.emptyMap(), VIEW_XML_1);

    registry.fireEvent(event);

    Assert.assertNull(listener.getLastEvent());
  }

  @Test
  public void testListener_allVersions() throws Exception {
    ViewRegistry registry = ViewRegistry.getInstance();

    TestListener listener = new TestListener();
    registry.registerListener(listener, "MY_VIEW", null); // all versions of MY_VIEW

    EventImpl event = EventImplTest.getEvent("MyEvent", Collections.emptyMap(), VIEW_XML_1);

    registry.fireEvent(event);

    Assert.assertEquals(event, listener.getLastEvent());

    listener.clear();

    // fire an event for a different view
    event = EventImplTest.getEvent("MyEvent", Collections.emptyMap(), VIEW_XML_2);

    registry.fireEvent(event);

    Assert.assertEquals(event, listener.getLastEvent());

    listener.clear();

    // un-register the listener
    registry.unregisterListener(listener, "MY_VIEW", null); // all versions of MY_VIEW

    event = EventImplTest.getEvent("MyEvent", Collections.emptyMap(), VIEW_XML_1);

    registry.fireEvent(event);

    Assert.assertNull(listener.getLastEvent());

    event = EventImplTest.getEvent("MyEvent", Collections.emptyMap(), VIEW_XML_2);

    registry.fireEvent(event);

    Assert.assertNull(listener.getLastEvent());
  }

  @Test
  public void testGetResourceProviders() throws Exception {

    ViewConfig config = ViewConfigTest.getConfig();

    ViewEntity viewDefinition = ViewEntityTest.getViewEntity();

    ViewRegistry registry = ViewRegistry.getInstance();

    viewDefinition.setConfiguration(config);

    registry.setupViewDefinition(viewDefinition, getClass().getClassLoader());

