AbstractCatalogEntityVisitor.java

/*
 * Portions of this software was developed by employees of the National Institute
 * of Standards and Technology (NIST), an agency of the Federal Government and is
 * being made available as a public service. Pursuant to title 17 United States
 * Code Section 105, works of NIST employees are not subject to copyright
 * protection in the United States. This software may be subject to foreign
 * copyright. Permission in the United States and in foreign countries, to the
 * extent that NIST may hold copyright, to use, copy, modify, create derivative
 * works, and distribute this software and its documentation without fee is hereby
 * granted on a non-exclusive basis, provided that this notice and disclaimer
 * of warranty appears in all copies.
 *
 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
 */

package gov.nist.secauto.oscal.lib.profile.resolver.support;

import gov.nist.secauto.metaschema.model.common.metapath.MetapathExpression;
import gov.nist.secauto.metaschema.model.common.metapath.item.IDocumentNodeItem;
import gov.nist.secauto.metaschema.model.common.metapath.item.IRequiredValueModelNodeItem;
import gov.nist.secauto.metaschema.model.common.metapath.item.IRootAssemblyNodeItem;
import gov.nist.secauto.metaschema.model.common.util.CollectionUtil;
import gov.nist.secauto.metaschema.model.common.util.ObjectUtils;

import java.util.Collections;
import java.util.EnumSet;
import java.util.Set;

import edu.umd.cs.findbugs.annotations.NonNull;

/**
 * Visits a catalog document and its children as designated.
 * <p>
 * This implementation is stateless. The {@code T} parameter can be used to
 * convey state as needed.
 *
 * @param <T>
 *          the state type
 * @param <R>
 *          the result type
 */
public abstract class AbstractCatalogEntityVisitor<T, R>
    extends AbstractCatalogVisitor<T, R> {
  @NonNull
  public static final MetapathExpression CHILD_PART_METAPATH
      = MetapathExpression.compile("part|part//part");
  @NonNull
  private static final MetapathExpression BACK_MATTER_RESOURCES_METAPATH
      = MetapathExpression.compile("back-matter/resource");
  @NonNull
  private static final Set<IEntityItem.ItemType> GROUP_CONTAINER_TYPES
      = ObjectUtils.notNull(EnumSet.of(
          IEntityItem.ItemType.GROUP,
          IEntityItem.ItemType.CONTROL,
          IEntityItem.ItemType.PARAMETER,
          IEntityItem.ItemType.PART));
  @NonNull
  private static final Set<IEntityItem.ItemType> CONTROL_CONTAINER_TYPES
      = ObjectUtils.notNull(EnumSet.of(
          IEntityItem.ItemType.CONTROL,
          IEntityItem.ItemType.PARAMETER,
          IEntityItem.ItemType.PART));
  @NonNull
  private final Set<IEntityItem.ItemType> itemTypesToVisit;

  /**
   * Create a new visitor that will visit the item types identified by
   * {@code itemTypesToVisit}.
   *
   * @param itemTypesToVisit
   *          the item type the visitor will visit
   */
  public AbstractCatalogEntityVisitor(@NonNull Set<IEntityItem.ItemType> itemTypesToVisit) {
    this.itemTypesToVisit = CollectionUtil.unmodifiableSet(itemTypesToVisit);
  }

  public Set<IEntityItem.ItemType> getItemTypesToVisit() {
    return CollectionUtil.unmodifiableSet(itemTypesToVisit);
  }

  protected boolean isVisitedItemType(@NonNull IEntityItem.ItemType type) {
    return itemTypesToVisit.contains(type);
  }

  @Override
  public R visitCatalog(IDocumentNodeItem catalogDocument, T state) {
    R result = super.visitCatalog(catalogDocument, state);

    IRootAssemblyNodeItem root = catalogDocument.getRootAssemblyNodeItem();
    visitMetadata(root, state);
    visitBackMatter(root, state);
    return result;
  }

  @Override
  protected R visitGroupContainer(IRequiredValueModelNodeItem catalogOrGroup, R initialResult, T state) {
    R retval;
    if (Collections.disjoint(getItemTypesToVisit(), GROUP_CONTAINER_TYPES)) {
      retval = initialResult;
    } else {
      retval = super.visitGroupContainer(catalogOrGroup, initialResult, state);
    }
    return retval;
  }

  @Override
  protected R visitControlContainer(IRequiredValueModelNodeItem catalogOrGroupOrControl, R initialResult, T state) {
    R retval;
    if (Collections.disjoint(getItemTypesToVisit(), CONTROL_CONTAINER_TYPES)) {
      retval = initialResult;
    } else {
      // first descend to all control container children
      retval = super.visitControlContainer(catalogOrGroupOrControl, initialResult, state);

      // handle parameters
      if (isVisitedItemType(IEntityItem.ItemType.PARAMETER)) {
        retval = catalogOrGroupOrControl.getModelItemsByName("param").stream()
            .map(paramItem -> {
              return visitParameter(ObjectUtils.requireNonNull(paramItem), catalogOrGroupOrControl, state);
            })
            .reduce(retval, (first, second) -> aggregateResults(first, second, state));
      } // TODO Auto-generated method stub
    }
    return retval;
  }

  protected void visitParts(@NonNull IRequiredValueModelNodeItem groupOrControlItem, T state) {
    // handle parts
    if (isVisitedItemType(IEntityItem.ItemType.PART)) {
      CHILD_PART_METAPATH.evaluate(groupOrControlItem).asStream()
          .map(item -> (IRequiredValueModelNodeItem) item)
          .forEachOrdered(partItem -> {
            visitPart(ObjectUtils.requireNonNull(partItem), groupOrControlItem, state);
          });
    }
  }

  @Override
  protected R visitGroupInternal(@NonNull IRequiredValueModelNodeItem item, R childResult, T state) {
    if (isVisitedItemType(IEntityItem.ItemType.PART)) {
      visitParts(item, state);
    }

    R retval = childResult;
    if (isVisitedItemType(IEntityItem.ItemType.GROUP)) {
      retval = visitGroup(item, retval, state);
    }
    return retval;
  }

  @Override
  protected R visitControlInternal(IRequiredValueModelNodeItem item, R childResult, T state) {
    if (isVisitedItemType(IEntityItem.ItemType.PART)) {
      visitParts(item, state);
    }

    R retval = childResult;
    if (isVisitedItemType(IEntityItem.ItemType.CONTROL)) {
      retval = visitControl(item, retval, state);
    }
    return retval;
  }

  /**
   * Called when visiting a parameter.
   * <p>
   * Can be overridden by classes extending this interface to support processing
   * of the visited object.
   *
   * @param item
   *          the Metapath item for the parameter
   * @param catalogOrGroupOrControl
   *          the parameter's parent Metapath item
   * @param state
   *          the calling context information
   * @return a meaningful result of the given type
   */
  protected R visitParameter(
      @NonNull IRequiredValueModelNodeItem item,
      @NonNull IRequiredValueModelNodeItem catalogOrGroupOrControl,
      T state) {
    // do nothing
    return newDefaultResult(state);
  }

  /**
   * Called when visiting a part.
   * <p>
   * Can be overridden by classes extending this interface to support processing
   * of the visited object.
   *
   * @param item
   *          the Metapath item for the part
   * @param groupOrControl
   *          the part's parent Metapath item
   * @param state
   *          the calling context information
   */
  protected void visitPart( // NOPMD noop default
      @NonNull IRequiredValueModelNodeItem item,
      @NonNull IRequiredValueModelNodeItem groupOrControl,
      T state) {
    // do nothing
  }

  /**
   * Called when visiting the "metadata" section of an OSCAL document.
   * <p>
   * Visits each contained role, location, and party.
   *
   * @param rootItem
   *          the root Metaschema node item containing the "metadata" node
   * @param state
   *          the calling context information
   */
  protected void visitMetadata(@NonNull IRootAssemblyNodeItem rootItem, T state) {
    rootItem.getModelItemsByName("metadata").forEach(metadataItem -> {
      if (isVisitedItemType(IEntityItem.ItemType.ROLE)) {
        metadataItem.getModelItemsByName("role").forEach(roleItem -> {
          visitRole(ObjectUtils.requireNonNull(roleItem), metadataItem, state);
        });
      }

      if (isVisitedItemType(IEntityItem.ItemType.LOCATION)) {
        metadataItem.getModelItemsByName("location").forEach(locationItem -> {
          visitLocation(ObjectUtils.requireNonNull(locationItem), metadataItem, state);
        });
      }

      if (isVisitedItemType(IEntityItem.ItemType.PARTY)) {
        metadataItem.getModelItemsByName("party").forEach(partyItem -> {
          visitParty(ObjectUtils.requireNonNull(partyItem), metadataItem, state);
        });
      }
    });
  }

  /**
   * Called when visiting a role in the "metadata" section of an OSCAL document.
   * <p>
   * Can be overridden by classes extending this interface to support processing
   * of the visited object.
   *
   * @param item
   *          the role Metaschema node item which is a child of the "metadata"
   *          node
   * @param metadataItem
   *          the "metadata" Metaschema node item containing the role
   * @param state
   *          the calling context information
   */
  protected void visitRole( // NOPMD noop default
      @NonNull IRequiredValueModelNodeItem item,
      @NonNull IRequiredValueModelNodeItem metadataItem,
      T state) {
    // do nothing
  }

  /**
   * Called when visiting a location in the "metadata" section of an OSCAL
   * document.
   * <p>
   * Can be overridden by classes extending this interface to support processing
   * of the visited object.
   *
   * @param item
   *          the location Metaschema node item which is a child of the "metadata"
   *          node
   * @param metadataItem
   *          the "metadata" Metaschema node item containing the location
   * @param state
   *          the calling context information
   */
  protected void visitLocation( // NOPMD noop default
      @NonNull IRequiredValueModelNodeItem item,
      @NonNull IRequiredValueModelNodeItem metadataItem,
      T state) {
    // do nothing
  }

  /**
   * Called when visiting a party in the "metadata" section of an OSCAL document.
   * <p>
   * Can be overridden by classes extending this interface to support processing
   * of the visited object.
   *
   * @param item
   *          the party Metaschema node item which is a child of the "metadata"
   *          node
   * @param metadataItem
   *          the "metadata" Metaschema node item containing the party
   * @param state
   *          the calling context information
   */
  protected void visitParty( // NOPMD noop default
      @NonNull IRequiredValueModelNodeItem item,
      @NonNull IRequiredValueModelNodeItem metadataItem,
      T state) {
    // do nothing
  }

  /**
   * Called when visiting the "back-matter" section of an OSCAL document.
   * <p>
   * Visits each contained resource.
   *
   * @param rootItem
   *          the root Metaschema node item containing the "back-matter" node
   * @param state
   *          the calling context information
   */
  protected void visitBackMatter(@NonNull IRootAssemblyNodeItem rootItem, T state) {
    if (isVisitedItemType(IEntityItem.ItemType.RESOURCE)) {
      BACK_MATTER_RESOURCES_METAPATH.evaluate(rootItem).asStream()
          .map(item -> (IRequiredValueModelNodeItem) item)
          .forEachOrdered(resourceItem -> {
            visitResource(ObjectUtils.requireNonNull(resourceItem), rootItem, state);
          });
    }
  }

  /**
   * Called when visiting a resource in the "back-matter" section of an OSCAL
   * document.
   * <p>
   * Can be overridden by classes extending this interface to support processing
   * of the visited object.
   *
   * @param item
   *          the resource Metaschema node item which is a child of the "metadata"
   *          node
   * @param backMatterItem
   *          the resource Metaschema node item containing the party
   * @param state
   *          the calling context information
   */
  protected void visitResource( // NOPMD noop default
      @NonNull IRequiredValueModelNodeItem item,
      @NonNull IRootAssemblyNodeItem backMatterItem,
      T state) {
    // do nothing
  }
}