1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.metaschema.core.model.constraint;
7   
8   import gov.nist.secauto.metaschema.core.metapath.DynamicContext;
9   import gov.nist.secauto.metaschema.core.metapath.IMetapathExpression;
10  import gov.nist.secauto.metaschema.core.metapath.item.IItem;
11  import gov.nist.secauto.metaschema.core.metapath.item.ISequence;
12  import gov.nist.secauto.metaschema.core.metapath.item.node.IDefinitionNodeItem;
13  import gov.nist.secauto.metaschema.core.metapath.item.node.IModuleNodeItem;
14  import gov.nist.secauto.metaschema.core.model.IDefinition;
15  import gov.nist.secauto.metaschema.core.model.IModelElementVisitor;
16  import gov.nist.secauto.metaschema.core.model.ISource;
17  import gov.nist.secauto.metaschema.core.qname.IEnhancedQName;
18  import gov.nist.secauto.metaschema.core.util.CollectionUtil;
19  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
20  
21  import org.apache.logging.log4j.LogManager;
22  import org.apache.logging.log4j.Logger;
23  
24  import java.util.Collections;
25  import java.util.HashMap;
26  import java.util.HashSet;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Set;
30  import java.util.stream.Collectors;
31  
32  import javax.xml.namespace.QName;
33  
34  import edu.umd.cs.findbugs.annotations.NonNull;
35  
36  /**
37   * The default implementation of a constraint set sourced from an external
38   * constraint resource.
39   */
40  public class ScopedConstraintSet implements IConstraintSet {
41    private static final Logger LOGGER = LogManager.getLogger(ScopedConstraintSet.class);
42    @NonNull
43    private final ISource source;
44    @NonNull
45    private final Set<IConstraintSet> importedConstraintSets;
46    @NonNull
47    private final Map<IEnhancedQName, List<IScopedContraints>> scopedContraints;
48    @NonNull
49    private final Set<IDefinition> previouslyTargetedDefinitions = new HashSet<>();
50  
51    /**
52     * Construct a new constraint set.
53     *
54     * @param source
55     *          the resource the constraint was provided from
56     * @param scopedContraints
57     *          a set of constraints qualified by a scope path
58     * @param importedConstraintSets
59     *          constraint sets imported by this constraint set
60     */
61    @SuppressWarnings("null")
62    public ScopedConstraintSet(
63        @NonNull ISource source,
64        @NonNull List<IScopedContraints> scopedContraints,
65        @NonNull Set<IConstraintSet> importedConstraintSets) {
66      this.source = source;
67      this.scopedContraints = scopedContraints.stream()
68          .collect(
69              Collectors.collectingAndThen(
70                  Collectors.groupingBy(
71                      scope -> IEnhancedQName.of(scope.getModuleNamespace().toString(), scope.getModuleShortName()),
72                      Collectors.toUnmodifiableList()),
73                  Collections::unmodifiableMap));
74      this.importedConstraintSets = CollectionUtil.unmodifiableSet(importedConstraintSets);
75    }
76  
77    /**
78     * Get the resource the constraint was provided from.
79     *
80     * @return the resource
81     */
82    @Override
83    public ISource getSource() {
84      return source;
85    }
86  
87    /**
88     * Get the set of Metaschema scoped constraints to apply by a {@link QName}
89     * formed from the Metaschema namespace and short name.
90     *
91     * @return the mapping of QName to scoped constraints
92     */
93    @NonNull
94    public Map<IEnhancedQName, List<IScopedContraints>> getScopedContraints() {
95      return scopedContraints;
96    }
97  
98    @Override
99    public Set<IConstraintSet> getImportedConstraintSets() {
100     return importedConstraintSets;
101   }
102 
103   @Override
104   public void applyConstraintsForModule(
105       IModuleNodeItem moduleItem,
106       IModelElementVisitor<ITargetedConstraints, Void> visitor) {
107     IEnhancedQName qname = moduleItem.getModule().getQName();
108     List<IScopedContraints> scopes = getScopedContraints().getOrDefault(qname, CollectionUtil.emptyList());
109 
110     @SuppressWarnings("PMD.UseConcurrentHashMap")
111     Map<IDefinition, Set<ITargetedConstraints>> definitionConstraints = new HashMap<>();
112 
113     DynamicContext dynamicContext = new DynamicContext(getSource().getStaticContext());
114 
115     for (IScopedContraints scoped : scopes) {
116       for (ITargetedConstraints targeted : scoped.getTargetedContraints()) {
117         for (IMetapathExpression metapath : targeted.getTargets()) {
118           ISequence<? extends IDefinitionNodeItem<?, ?>> items = ISequence.of(ObjectUtils.notNull(
119               metapath.evaluate(moduleItem, dynamicContext).stream()
120                   .filter(item -> filterNonDefinitionItem(item, metapath))
121                   .map(item -> (IDefinitionNodeItem<?, ?>) item)))
122               .reusable();
123           assert items != null;
124 
125           Set<IDefinition> targetedDefinitions = items.stream()
126               .map(IDefinitionNodeItem::getDefinition)
127               .filter(definition -> !previouslyTargetedDefinitions.contains(definition))
128               .collect(Collectors.toUnmodifiableSet());
129 
130           targetedDefinitions.forEach(definition -> {
131             definitionConstraints.compute(definition, (key, value) -> {
132               Set<ITargetedConstraints> targets = value == null ? new HashSet<>() : value;
133               targets.add(targeted);
134               return targets;
135             });
136           });
137         }
138       }
139     }
140 
141     for (Map.Entry<IDefinition, Set<ITargetedConstraints>> entry : definitionConstraints.entrySet()) {
142       IDefinition definition = entry.getKey();
143       for (ITargetedConstraints constraints : entry.getValue()) {
144         definition.accept(visitor, constraints);
145       }
146     }
147     previouslyTargetedDefinitions.addAll(ObjectUtils.notNull(definitionConstraints.keySet()));
148   }
149 
150   private static boolean filterNonDefinitionItem(IItem item, @NonNull IMetapathExpression metapath) {
151     boolean retval = item instanceof IDefinitionNodeItem;
152     if (!retval) {
153       LOGGER.atError().log(
154           "Found non-definition item '{}' while applying external constraints using target expression '{}'.",
155           item.toString(),
156           metapath.getPath());
157     }
158     return retval;
159   }
160 }