/**
   * Retrieves the View instance with the given id.
   *
   * <p>Invokes {@link UifDictionaryIndex#getImmutableViewById(java.lang.String)} to get the view
   * singleton from spring then returns a copy.
   *
   * @param viewId the unique id for the view
   * @return View instance with the given id
   * @throws org.kuali.rice.krad.datadictionary.DataDictionaryException if view doesn't exist for id
   */
  public View getViewById(final String viewId) {
    // check for preloaded view
    if (viewPools.containsKey(viewId)) {
      final UifViewPool viewPool = viewPools.get(viewId);
      synchronized (viewPool) {
        if (!viewPool.isEmpty()) {
          View view = viewPool.getViewInstance();

          // replace view in the pool
          Runnable createView =
              new Runnable() {
                public void run() {
                  View newViewInstance = CopyUtils.copy(getImmutableViewById(viewId));
                  viewPool.addViewInstance(newViewInstance);
                }
              };

          Thread t = new Thread(createView);
          t.start();

          return view;
        } else {
          LOG.info(
              "Pool size for view with id: "
                  + viewId
                  + " is empty. Considering increasing max pool size.");
        }
      }
    }

    View view = getImmutableViewById(viewId);

    return CopyUtils.copy(view);
  }
  /**
   * Initializes the view index {@code Map} then iterates through all the beans in the factory that
   * implement {@code View}, adding them to the index
   */
  protected void buildViewIndicies() {
    LOG.info("Starting View Index Building");

    viewBeanEntriesById = new HashMap<String, String>();
    viewEntriesByType = new HashMap<String, ViewTypeDictionaryIndex>();
    viewPools = new HashMap<String, UifViewPool>();

    boolean inDevMode =
        Boolean.parseBoolean(
            ConfigContext.getCurrentContextConfig()
                .getProperty(KRADConstants.ConfigParameters.KRAD_DEV_MODE));

    ExecutorService executor = Executors.newFixedThreadPool(threadPoolSize);

    String[] beanNames = ddBeans.getBeanNamesForType(View.class);
    for (final String beanName : beanNames) {
      BeanDefinition beanDefinition = ddBeans.getMergedBeanDefinition(beanName);
      PropertyValues propertyValues = beanDefinition.getPropertyValues();

      String id = ViewModelUtils.getStringValFromPVs(propertyValues, "id");
      if (StringUtils.isBlank(id)) {
        id = beanName;
      }

      if (viewBeanEntriesById.containsKey(id)) {
        throw new DataDictionaryException(
            "Two views must not share the same id. Found duplicate id: " + id);
      }

      viewBeanEntriesById.put(id, beanName);

      indexViewForType(propertyValues, id);

      // pre-load views if necessary
      if (!inDevMode) {
        String poolSizeStr = ViewModelUtils.getStringValFromPVs(propertyValues, "preloadPoolSize");
        if (StringUtils.isNotBlank(poolSizeStr)) {
          int poolSize = Integer.parseInt(poolSizeStr);
          if (poolSize < 1) {
            continue;
          }

          final View view = (View) ddBeans.getBean(beanName);
          final UifViewPool viewPool = new UifViewPool();
          viewPool.setMaxSize(poolSize);
          for (int j = 0; j < poolSize; j++) {
            Runnable createView =
                new Runnable() {
                  @Override
                  public void run() {
                    viewPool.addViewInstance((View) CopyUtils.copy(view));
                  }
                };

            executor.execute(createView);
          }
          viewPools.put(id, viewPool);
        }
      }
    }

    executor.shutdown();

    LOG.info("Completed View Index Building");
  }