001/* 002 * SPDX-FileCopyrightText: none 003 * SPDX-License-Identifier: CC0-1.0 004 */ 005 006package dev.metaschema.oscal.lib.profile.resolver.support; 007 008import java.util.Collections; 009import java.util.EnumSet; 010import java.util.Set; 011 012import dev.metaschema.core.metapath.IMetapathExpression; 013import dev.metaschema.core.metapath.item.node.IAssemblyNodeItem; 014import dev.metaschema.core.metapath.item.node.IDocumentNodeItem; 015import dev.metaschema.core.metapath.item.node.IRootAssemblyNodeItem; 016import dev.metaschema.core.util.CollectionUtil; 017import dev.metaschema.core.util.ObjectUtils; 018import dev.metaschema.oscal.lib.OscalBindingContext; 019import dev.metaschema.oscal.lib.OscalModelConstants; 020import edu.umd.cs.findbugs.annotations.NonNull; 021 022/** 023 * Visits a catalog document and its children as designated. 024 * <p> 025 * This implementation is stateless. The {@code T} parameter can be used to 026 * convey state as needed. 027 * 028 * @param <T> 029 * the state type 030 * @param <R> 031 * the result type 032 */ 033public abstract class AbstractCatalogEntityVisitor<T, R> 034 extends AbstractCatalogVisitor<T, R> { 035 @NonNull 036 public static final IMetapathExpression CHILD_PART_METAPATH 037 = IMetapathExpression.compile("part|part//part", 038 OscalBindingContext.OSCAL_STATIC_METAPATH_CONTEXT); 039 @NonNull 040 private static final IMetapathExpression BACK_MATTER_RESOURCES_METAPATH 041 = IMetapathExpression.compile("back-matter/resource", 042 OscalBindingContext.OSCAL_STATIC_METAPATH_CONTEXT); 043 @NonNull 044 private static final Set<IEntityItem.ItemType> GROUP_CONTAINER_TYPES 045 = ObjectUtils.notNull(EnumSet.of( 046 IEntityItem.ItemType.GROUP, 047 IEntityItem.ItemType.CONTROL, 048 IEntityItem.ItemType.PARAMETER, 049 IEntityItem.ItemType.PART)); 050 @NonNull 051 private static final Set<IEntityItem.ItemType> CONTROL_CONTAINER_TYPES 052 = ObjectUtils.notNull(EnumSet.of( 053 IEntityItem.ItemType.CONTROL, 054 IEntityItem.ItemType.PARAMETER, 055 IEntityItem.ItemType.PART)); 056 @NonNull 057 private final Set<IEntityItem.ItemType> itemTypesToVisit; 058 059 /** 060 * Create a new visitor that will visit the item types identified by 061 * {@code itemTypesToVisit}. 062 * 063 * @param itemTypesToVisit 064 * the item type the visitor will visit 065 */ 066 public AbstractCatalogEntityVisitor(@NonNull Set<IEntityItem.ItemType> itemTypesToVisit) { 067 this.itemTypesToVisit = CollectionUtil.unmodifiableSet(itemTypesToVisit); 068 } 069 070 public Set<IEntityItem.ItemType> getItemTypesToVisit() { 071 return CollectionUtil.unmodifiableSet(itemTypesToVisit); 072 } 073 074 protected boolean isVisitedItemType(@NonNull IEntityItem.ItemType type) { 075 return itemTypesToVisit.contains(type); 076 } 077 078 @Override 079 public R visitCatalog(IDocumentNodeItem catalogDocument, T state) { 080 R result = super.visitCatalog(catalogDocument, state); 081 082 catalogDocument.modelItems().forEachOrdered(item -> { 083 IRootAssemblyNodeItem root = ObjectUtils.requireNonNull((IRootAssemblyNodeItem) item); 084 visitMetadata(root, state); 085 visitBackMatter(root, state); 086 }); 087 return result; 088 } 089 090 @Override 091 protected R visitGroupContainer(IAssemblyNodeItem catalogOrGroup, R initialResult, T state) { 092 R retval; 093 if (Collections.disjoint(getItemTypesToVisit(), GROUP_CONTAINER_TYPES)) { 094 retval = initialResult; 095 } else { 096 retval = super.visitGroupContainer(catalogOrGroup, initialResult, state); 097 } 098 return retval; 099 } 100 101 @Override 102 protected R visitControlContainer(IAssemblyNodeItem catalogOrGroupOrControl, R initialResult, T state) { 103 R retval; 104 if (Collections.disjoint(getItemTypesToVisit(), CONTROL_CONTAINER_TYPES)) { 105 retval = initialResult; 106 } else { 107 // first descend to all control container children 108 retval = super.visitControlContainer(catalogOrGroupOrControl, initialResult, state); 109 110 // handle parameters 111 if (isVisitedItemType(IEntityItem.ItemType.PARAMETER)) { 112 retval = catalogOrGroupOrControl.getModelItemsByName(OscalModelConstants.QNAME_PARAM).stream() 113 .map(paramItem -> visitParameter( 114 ObjectUtils.requireNonNull((IAssemblyNodeItem) paramItem), 115 catalogOrGroupOrControl, 116 state)) 117 .reduce(retval, (first, second) -> aggregateResults(first, second, state)); 118 } 119 } 120 return retval; 121 } 122 123 protected void visitParts(@NonNull IAssemblyNodeItem groupOrControlItem, T state) { 124 // handle parts 125 if (isVisitedItemType(IEntityItem.ItemType.PART)) { 126 CHILD_PART_METAPATH.evaluate(groupOrControlItem).stream() 127 .map(item -> (IAssemblyNodeItem) item) 128 .forEachOrdered(partItem -> { 129 visitPart(ObjectUtils.requireNonNull(partItem), groupOrControlItem, state); 130 }); 131 } 132 } 133 134 @Override 135 protected R visitGroupInternal(@NonNull IAssemblyNodeItem item, R childResult, T state) { 136 if (isVisitedItemType(IEntityItem.ItemType.PART)) { 137 visitParts(item, state); 138 } 139 140 R retval = childResult; 141 if (isVisitedItemType(IEntityItem.ItemType.GROUP)) { 142 retval = visitGroup(item, retval, state); 143 } 144 return retval; 145 } 146 147 @Override 148 protected R visitControlInternal(IAssemblyNodeItem item, R childResult, T state) { 149 if (isVisitedItemType(IEntityItem.ItemType.PART)) { 150 visitParts(item, state); 151 } 152 153 R retval = childResult; 154 if (isVisitedItemType(IEntityItem.ItemType.CONTROL)) { 155 retval = visitControl(item, retval, state); 156 } 157 return retval; 158 } 159 160 /** 161 * Called when visiting a parameter. 162 * <p> 163 * Can be overridden by classes extending this interface to support processing 164 * of the visited object. 165 * 166 * @param item 167 * the Metapath item for the parameter 168 * @param catalogOrGroupOrControl 169 * the parameter's parent Metapath item 170 * @param state 171 * the calling context information 172 * @return a meaningful result of the given type 173 */ 174 protected R visitParameter( 175 @NonNull IAssemblyNodeItem item, 176 @NonNull IAssemblyNodeItem catalogOrGroupOrControl, 177 T state) { 178 // do nothing 179 return newDefaultResult(state); 180 } 181 182 /** 183 * Called when visiting a part. 184 * <p> 185 * Can be overridden by classes extending this interface to support processing 186 * of the visited object. 187 * 188 * @param item 189 * the Metapath item for the part 190 * @param groupOrControl 191 * the part's parent Metapath item 192 * @param state 193 * the calling context information 194 */ 195 protected void visitPart( // NOPMD noop default 196 @NonNull IAssemblyNodeItem item, 197 @NonNull IAssemblyNodeItem groupOrControl, 198 T state) { 199 // do nothing 200 } 201 202 /** 203 * Called when visiting the "metadata" section of an OSCAL document. 204 * <p> 205 * Visits each contained role, location, and party. 206 * 207 * @param rootItem 208 * the root Module node item containing the "metadata" node 209 * @param state 210 * the calling context information 211 */ 212 protected void visitMetadata(@NonNull IRootAssemblyNodeItem rootItem, T state) { 213 rootItem.getModelItemsByName(OscalModelConstants.QNAME_METADATA).stream() 214 .map(metadataItem -> (IAssemblyNodeItem) metadataItem) 215 .forEach(metadataItem -> { 216 if (isVisitedItemType(IEntityItem.ItemType.ROLE)) { 217 metadataItem.getModelItemsByName(OscalModelConstants.QNAME_ROLE).stream() 218 .map(roleItem -> (IAssemblyNodeItem) roleItem) 219 .forEachOrdered(roleItem -> { 220 visitRole(ObjectUtils.requireNonNull(roleItem), metadataItem, state); 221 }); 222 } 223 224 if (isVisitedItemType(IEntityItem.ItemType.LOCATION)) { 225 metadataItem.getModelItemsByName(OscalModelConstants.QNAME_LOCATION).stream() 226 .map(locationItem -> (IAssemblyNodeItem) locationItem) 227 .forEachOrdered(locationItem -> { 228 visitLocation(ObjectUtils.requireNonNull(locationItem), metadataItem, state); 229 }); 230 } 231 232 if (isVisitedItemType(IEntityItem.ItemType.PARTY)) { 233 metadataItem.getModelItemsByName(OscalModelConstants.QNAME_PARTY).stream() 234 .map(partyItem -> (IAssemblyNodeItem) partyItem) 235 .forEachOrdered(partyItem -> { 236 visitParty(ObjectUtils.requireNonNull(partyItem), metadataItem, state); 237 }); 238 } 239 }); 240 } 241 242 /** 243 * Called when visiting a role in the "metadata" section of an OSCAL document. 244 * <p> 245 * Can be overridden by classes extending this interface to support processing 246 * of the visited object. 247 * 248 * @param item 249 * the role Module node item which is a child of the "metadata" node 250 * @param metadataItem 251 * the "metadata" Module node item containing the role 252 * @param state 253 * the calling context information 254 */ 255 protected void visitRole( // NOPMD noop default 256 @NonNull IAssemblyNodeItem item, 257 @NonNull IAssemblyNodeItem metadataItem, 258 T state) { 259 // do nothing 260 } 261 262 /** 263 * Called when visiting a location in the "metadata" section of an OSCAL 264 * document. 265 * <p> 266 * Can be overridden by classes extending this interface to support processing 267 * of the visited object. 268 * 269 * @param item 270 * the location Module node item which is a child of the "metadata" 271 * node 272 * @param metadataItem 273 * the "metadata" Module node item containing the location 274 * @param state 275 * the calling context information 276 */ 277 protected void visitLocation( // NOPMD noop default 278 @NonNull IAssemblyNodeItem item, 279 @NonNull IAssemblyNodeItem metadataItem, 280 T state) { 281 // do nothing 282 } 283 284 /** 285 * Called when visiting a party in the "metadata" section of an OSCAL document. 286 * <p> 287 * Can be overridden by classes extending this interface to support processing 288 * of the visited object. 289 * 290 * @param item 291 * the party Module node item which is a child of the "metadata" node 292 * @param metadataItem 293 * the "metadata" Module node item containing the party 294 * @param state 295 * the calling context information 296 */ 297 protected void visitParty( // NOPMD noop default 298 @NonNull IAssemblyNodeItem item, 299 @NonNull IAssemblyNodeItem metadataItem, 300 T state) { 301 // do nothing 302 } 303 304 /** 305 * Called when visiting the "back-matter" section of an OSCAL document. 306 * <p> 307 * Visits each contained resource. 308 * 309 * @param rootItem 310 * the root Module node item containing the "back-matter" node 311 * @param state 312 * the calling context information 313 */ 314 protected void visitBackMatter(@NonNull IRootAssemblyNodeItem rootItem, T state) { 315 if (isVisitedItemType(IEntityItem.ItemType.RESOURCE)) { 316 BACK_MATTER_RESOURCES_METAPATH.evaluate(rootItem).stream() 317 .map(item -> (IAssemblyNodeItem) item) 318 .forEachOrdered(resourceItem -> { 319 visitResource(ObjectUtils.requireNonNull(resourceItem), rootItem, state); 320 }); 321 } 322 } 323 324 /** 325 * Called when visiting a resource in the "back-matter" section of an OSCAL 326 * document. 327 * <p> 328 * Can be overridden by classes extending this interface to support processing 329 * of the visited object. 330 * 331 * @param resource 332 * the resource Module node item which is a child of the "metadata" 333 * node 334 * @param backMatter 335 * the resource Module node item containing the party 336 * @param state 337 * the calling context information 338 */ 339 protected void visitResource( // NOPMD noop default 340 @NonNull IAssemblyNodeItem resource, 341 @NonNull IRootAssemblyNodeItem backMatter, 342 T state) { 343 // do nothing 344 } 345}