001/* 002 * SPDX-FileCopyrightText: none 003 * SPDX-License-Identifier: CC0-1.0 004 */ 005 006package dev.metaschema.oscal.lib.profile.resolver.selection; 007 008import com.fasterxml.jackson.core.Version; 009import com.fasterxml.jackson.core.util.VersionUtil; 010 011import java.net.URI; 012import java.util.LinkedList; 013import java.util.List; 014import java.util.stream.Collectors; 015 016import dev.metaschema.core.metapath.item.node.IAssemblyNodeItem; 017import dev.metaschema.core.metapath.item.node.IDocumentNodeItem; 018import dev.metaschema.core.metapath.item.node.INodeItem; 019import dev.metaschema.core.metapath.item.node.IRootAssemblyNodeItem; 020import dev.metaschema.core.util.CollectionUtil; 021import dev.metaschema.core.util.ObjectUtils; 022import dev.metaschema.oscal.lib.model.BackMatter; 023import dev.metaschema.oscal.lib.model.BackMatter.Resource; 024import dev.metaschema.oscal.lib.model.Catalog; 025import dev.metaschema.oscal.lib.model.CatalogGroup; 026import dev.metaschema.oscal.lib.model.Control; 027import dev.metaschema.oscal.lib.model.Metadata; 028import dev.metaschema.oscal.lib.model.Metadata.Location; 029import dev.metaschema.oscal.lib.model.Metadata.Party; 030import dev.metaschema.oscal.lib.model.Metadata.Role; 031import dev.metaschema.oscal.lib.model.Parameter; 032import dev.metaschema.oscal.lib.model.ProfileImport; 033import dev.metaschema.oscal.lib.profile.resolver.ProfileResolutionEvaluationException; 034import dev.metaschema.oscal.lib.profile.resolver.ProfileResolutionException; 035import dev.metaschema.oscal.lib.profile.resolver.ProfileResolver.UriResolver; 036import dev.metaschema.oscal.lib.profile.resolver.policy.ReferenceCountingVisitor; 037import dev.metaschema.oscal.lib.profile.resolver.support.BasicIndexer; 038import dev.metaschema.oscal.lib.profile.resolver.support.IEntityItem; 039import dev.metaschema.oscal.lib.profile.resolver.support.IIndexer; 040import dev.metaschema.oscal.lib.profile.resolver.support.IIndexer.SelectionStatus; 041import edu.umd.cs.findbugs.annotations.NonNull; 042 043public class Import { 044 045 @NonNull 046 private final IRootAssemblyNodeItem profile; 047 @NonNull 048 private final IAssemblyNodeItem profileImportItem; 049 050 public Import( 051 @NonNull IRootAssemblyNodeItem profile, 052 @NonNull IAssemblyNodeItem profileImportItem) { 053 054 this.profile = profile; 055 this.profileImportItem = profileImportItem; 056 } 057 058 protected IRootAssemblyNodeItem getProfileItem() { 059 return profile; 060 } 061 062 protected IAssemblyNodeItem getProfileImportItem() { 063 return profileImportItem; 064 } 065 066 @NonNull 067 protected ProfileImport getProfileImport() { 068 return ObjectUtils.requireNonNull((ProfileImport) profileImportItem.getValue()); 069 } 070 071 private static Catalog toCatalog(@NonNull IDocumentNodeItem catalogDocument) { 072 return (Catalog) INodeItem.toValue(catalogDocument); 073 } 074 075 @NonNull 076 protected IControlFilter newControlFilter() { 077 return IControlFilter.newInstance(getProfileImport()); 078 } 079 080 @NonNull 081 protected IIndexer newIndexer() { 082 // TODO: add support for reassignment 083 // IIdentifierMapper mapper = IIdentifierMapper.IDENTITY; 084 // IIndexer indexer = new ReassignmentIndexer(mapper); 085 return new BasicIndexer(); 086 } 087 088 @NonNull 089 public IIndexer resolve( 090 @NonNull IDocumentNodeItem importedCatalogDocument, 091 @NonNull Catalog resolvedCatalog, 092 @NonNull UriResolver uriResolver) 093 throws ProfileResolutionException { 094 ProfileImport profileImport = getProfileImport(); 095 URI uri = ObjectUtils.requireNonNull(profileImport.getHref(), "profile import href is null"); 096 097 // determine which controls and groups to keep 098 IControlFilter filter = newControlFilter(); 099 IIndexer indexer = newIndexer(); 100 // the catalog is always selected. This ensures that controls defined at the 101 // catalog level are kept and not duplicated 102 indexer.setSelectionStatus(importedCatalogDocument.getRootAssemblyNodeItem(), SelectionStatus.SELECTED); 103 IControlSelectionState state = new ControlSelectionState(indexer, filter); 104 105 try { 106 ControlSelectionVisitor.instance().visitCatalog(importedCatalogDocument, state); 107 108 // process references 109 ReferenceCountingVisitor.instance().visitCatalog(importedCatalogDocument, indexer, uriResolver); 110 111 // filter based on selections 112 FilterNonSelectedVisitor.instance().visitCatalog(importedCatalogDocument, indexer); 113 } catch (ProfileResolutionEvaluationException ex) { 114 throw new ProfileResolutionException( 115 String.format("Import: Unable to resolve profile import '%s'. %s", uri.toString(), ex.getMessage()), ex); 116 } 117 118 Catalog importedCatalog = toCatalog(importedCatalogDocument); 119 for (Parameter param : CollectionUtil.listOrEmpty(importedCatalog.getParams())) { 120 if (param != null) { 121 resolvedCatalog.addParam(param); 122 } 123 } 124 for (Control control : CollectionUtil.listOrEmpty(importedCatalog.getControls())) { 125 if (control != null) { 126 resolvedCatalog.addControl(control); 127 } 128 } 129 for (CatalogGroup group : CollectionUtil.listOrEmpty(importedCatalog.getGroups())) { 130 if (group != null) { 131 resolvedCatalog.addGroup(group); 132 } 133 } 134 135 generateMetadata(importedCatalogDocument, resolvedCatalog, indexer); 136 generateBackMatter(importedCatalogDocument, resolvedCatalog, indexer); 137 return indexer; 138 } 139 140 private static void generateMetadata( 141 @NonNull IDocumentNodeItem importedCatalogDocument, 142 @NonNull Catalog resolvedCatalog, 143 @NonNull IIndexer indexer) { 144 Metadata importedMetadata = toCatalog(importedCatalogDocument).getMetadata(); 145 146 if (importedMetadata != null) { 147 resolveMetadata(importedMetadata, resolvedCatalog.getMetadata(), indexer); 148 } 149 } 150 151 private static void resolveMetadata( 152 @NonNull Metadata imported, 153 @NonNull Metadata resolved, 154 @NonNull IIndexer indexer) { 155 String importedVersion = imported.getOscalVersion(); 156 if (importedVersion != null) { 157 Version importOscalVersion = VersionUtil.parseVersion(importedVersion, null, null); 158 159 Version resolvedCatalogVersion 160 = VersionUtil.parseVersion(resolved.getOscalVersion(), null, null); 161 162 if (importOscalVersion.compareTo(resolvedCatalogVersion) > 0) { 163 resolved.setOscalVersion(importOscalVersion.toString()); 164 } 165 } 166 167 // copy roles, parties, and locations with prop name:keep and any referenced 168 resolved.setRoles( 169 IIndexer.filterDistinct( 170 ObjectUtils.notNull(CollectionUtil.listOrEmpty(resolved.getRoles()).stream()), 171 indexer.getEntitiesByItemType(IEntityItem.ItemType.ROLE), 172 Role::getId) 173 .collect(Collectors.toCollection(LinkedList::new))); 174 resolved.setParties( 175 IIndexer.filterDistinct( 176 ObjectUtils.notNull(CollectionUtil.listOrEmpty(resolved.getParties()).stream()), 177 indexer.getEntitiesByItemType(IEntityItem.ItemType.PARTY), 178 Party::getUuid) 179 .collect(Collectors.toCollection(LinkedList::new))); 180 resolved.setLocations( 181 IIndexer.filterDistinct( 182 ObjectUtils.notNull(CollectionUtil.listOrEmpty(resolved.getLocations()).stream()), 183 indexer.getEntitiesByItemType(IEntityItem.ItemType.LOCATION), 184 Location::getUuid) 185 .collect(Collectors.toCollection(LinkedList::new))); 186 } 187 188 @SuppressWarnings("PMD.AvoidDeeplyNestedIfStmts") // not worth a function call 189 private static void generateBackMatter( 190 @NonNull IDocumentNodeItem importedCatalogDocument, 191 @NonNull Catalog resolvedCatalog, 192 @NonNull IIndexer indexer) { 193 BackMatter importedBackMatter = toCatalog(importedCatalogDocument).getBackMatter(); 194 195 if (importedBackMatter != null) { 196 BackMatter resolvedBackMatter = resolvedCatalog.getBackMatter(); 197 198 List<Resource> resolvedResources = resolvedBackMatter == null ? CollectionUtil.emptyList() 199 : CollectionUtil.listOrEmpty(resolvedBackMatter.getResources()); 200 201 List<Resource> resources = IIndexer.filterDistinct( 202 ObjectUtils.notNull(resolvedResources.stream()), 203 indexer.getEntitiesByItemType(IEntityItem.ItemType.RESOURCE), 204 Resource::getUuid) 205 .collect(Collectors.toCollection(LinkedList::new)); 206 207 if (!resources.isEmpty()) { 208 if (resolvedBackMatter == null) { 209 resolvedBackMatter = new BackMatter(); 210 resolvedCatalog.setBackMatter(resolvedBackMatter); 211 } 212 213 resolvedBackMatter.setResources(resources); 214 } 215 } 216 } 217}