001/*
002 * Portions of this software was developed by employees of the National Institute
003 * of Standards and Technology (NIST), an agency of the Federal Government and is
004 * being made available as a public service. Pursuant to title 17 United States
005 * Code Section 105, works of NIST employees are not subject to copyright
006 * protection in the United States. This software may be subject to foreign
007 * copyright. Permission in the United States and in foreign countries, to the
008 * extent that NIST may hold copyright, to use, copy, modify, create derivative
009 * works, and distribute this software and its documentation without fee is hereby
010 * granted on a non-exclusive basis, provided that this notice and disclaimer
011 * of warranty appears in all copies.
012 *
013 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
014 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
015 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
016 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
017 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
018 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
019 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
020 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
021 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
022 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
023 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
024 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
025 */
026
027package gov.nist.secauto.metaschema.core.model.xml;
028
029import gov.nist.secauto.metaschema.core.metapath.MetapathExpression;
030import gov.nist.secauto.metaschema.core.model.MetaschemaException;
031import gov.nist.secauto.metaschema.core.model.constraint.DefaultConstraintSet;
032import gov.nist.secauto.metaschema.core.model.constraint.DefaultScopedContraints;
033import gov.nist.secauto.metaschema.core.model.constraint.IConstraint.ExternalSource;
034import gov.nist.secauto.metaschema.core.model.constraint.IConstraint.ISource;
035import gov.nist.secauto.metaschema.core.model.constraint.IConstraintSet;
036import gov.nist.secauto.metaschema.core.model.constraint.IScopedContraints;
037import gov.nist.secauto.metaschema.core.model.constraint.ITargetedConstaints;
038import gov.nist.secauto.metaschema.core.model.xml.xmlbeans.METASCHEMACONSTRAINTSDocument;
039import gov.nist.secauto.metaschema.core.model.xml.xmlbeans.METASCHEMACONSTRAINTSDocument.METASCHEMACONSTRAINTS.Scope;
040import gov.nist.secauto.metaschema.core.model.xml.xmlbeans.ScopedIndexHasKeyConstraintType;
041import gov.nist.secauto.metaschema.core.util.CollectionUtil;
042import gov.nist.secauto.metaschema.core.util.ObjectUtils;
043
044import org.apache.xmlbeans.XmlCursor;
045import org.apache.xmlbeans.XmlException;
046import org.apache.xmlbeans.XmlObject;
047import org.apache.xmlbeans.XmlOptions;
048
049import java.io.IOException;
050import java.net.URI;
051import java.util.Collection;
052import java.util.Collections;
053import java.util.Deque;
054import java.util.LinkedHashMap;
055import java.util.LinkedHashSet;
056import java.util.LinkedList;
057import java.util.List;
058import java.util.Map;
059
060import edu.umd.cs.findbugs.annotations.NonNull;
061
062/**
063 * Provides methods to load a constraint set expressed in XML.
064 * <p>
065 * Loaded constraint instances are cached to avoid the need to load them for
066 * every use. Any constraint set imported is also loaded and cached
067 * automatically.
068 */
069public class ConstraintLoader
070    extends AbstractLoader<IConstraintSet> {
071
072  @Override
073  protected IConstraintSet parseResource(@NonNull URI resource, @NonNull Deque<URI> visitedResources)
074      throws IOException {
075
076    // parse this metaschema
077    METASCHEMACONSTRAINTSDocument xmlObject = parseConstraintSet(resource);
078
079    // now check if this constraint set imports other constraint sets
080    int size = xmlObject.getMETASCHEMACONSTRAINTS().sizeOfImportArray();
081    @NonNull Map<URI, IConstraintSet> importedConstraints;
082    if (size == 0) {
083      importedConstraints = ObjectUtils.notNull(Collections.emptyMap());
084    } else {
085      try {
086        importedConstraints = new LinkedHashMap<>();
087        for (METASCHEMACONSTRAINTSDocument.METASCHEMACONSTRAINTS.Import imported : xmlObject.getMETASCHEMACONSTRAINTS()
088            .getImportList()) {
089          URI importedResource = URI.create(imported.getHref());
090          importedResource = ObjectUtils.notNull(resource.resolve(importedResource));
091          importedConstraints.put(importedResource, loadInternal(importedResource, visitedResources));
092        }
093      } catch (MetaschemaException ex) {
094        throw new IOException(ex);
095      }
096    }
097
098    // now create this metaschema
099    Collection<IConstraintSet> values = importedConstraints.values();
100    return new DefaultConstraintSet(resource, parseScopedConstraints(xmlObject, resource), new LinkedHashSet<>(values));
101  }
102
103  /**
104   * Parse the provided XML resource as a Metaschema constraints.
105   *
106   * @param resource
107   *          the resource to parse
108   * @return the XMLBeans representation of the Metaschema contraints
109   * @throws IOException
110   *           if a parsing error occurred
111   */
112  @NonNull
113  protected METASCHEMACONSTRAINTSDocument parseConstraintSet(@NonNull URI resource) throws IOException {
114    try {
115      XmlOptions options = new XmlOptions();
116      options.setBaseURI(resource);
117      options.setLoadLineNumbers();
118      return ObjectUtils.notNull(METASCHEMACONSTRAINTSDocument.Factory.parse(resource.toURL(), options));
119    } catch (XmlException ex) {
120      throw new IOException(ex);
121    }
122  }
123
124  /**
125   * Parse individual constraint definitions from the provided XMLBeans object.
126   *
127   * @param xmlObject
128   *          the XMLBeans object
129   * @param source
130   *          the source of the constraint content
131   * @return the scoped constraint definitions
132   */
133  @NonNull
134  protected List<IScopedContraints> parseScopedConstraints(
135      @NonNull METASCHEMACONSTRAINTSDocument xmlObject,
136      @NonNull URI source) {
137    List<IScopedContraints> scopedConstraints = new LinkedList<>();
138    ISource constraintSource = ExternalSource.instance(source);
139
140    for (Scope scope : xmlObject.getMETASCHEMACONSTRAINTS().getScopeList()) {
141      URI namespace = ObjectUtils.notNull(URI.create(scope.getMetaschemaNamespace()));
142      String shortName = ObjectUtils.requireNonNull(scope.getMetaschemaShortName());
143
144      try (XmlCursor cursor = scope.newCursor()) {
145        cursor.selectPath("declare namespace m='http://csrc.nist.gov/ns/oscal/metaschema/1.0';"
146            + "$this/m:assembly|$this/m:field|$this/m:flag");
147
148        List<ITargetedConstaints> targetedConstraints = new LinkedList<>(); // NOPMD - intentional
149        while (cursor.toNextSelection()) {
150          XmlObject obj = cursor.getObject();
151          if (obj instanceof Scope.Assembly) {
152            Scope.Assembly assembly = (Scope.Assembly) obj;
153            MetapathExpression expression = ObjectUtils.requireNonNull(assembly.getTarget());
154            AssemblyConstraintSupport constraints
155                = new AssemblyConstraintSupport(assembly, constraintSource); // NOPMD - intentional
156            targetedConstraints.add(new AssemblyTargetedConstraints(expression, constraints));
157          } else if (obj instanceof Scope.Field) {
158            Scope.Field field = (Scope.Field) obj;
159            MetapathExpression expression = ObjectUtils.requireNonNull(field.getTarget());
160            ValueConstraintSupport constraints
161                = new ValueConstraintSupport(field, constraintSource); // NOPMD - intentional
162            targetedConstraints.add(new FieldTargetedConstraints(expression, constraints));
163          } else if (obj instanceof ScopedIndexHasKeyConstraintType) {
164            Scope.Flag flag = (Scope.Flag) obj;
165            MetapathExpression expression = ObjectUtils.requireNonNull(flag.getTarget());
166            ValueConstraintSupport constraints
167                = new ValueConstraintSupport(flag, constraintSource); // NOPMD - intentional
168            targetedConstraints.add(new FlagTargetedConstraints(expression, constraints));
169          }
170        }
171        scopedConstraints.add(
172            new DefaultScopedContraints(namespace, shortName, CollectionUtil.unmodifiableList(targetedConstraints)));
173      }
174    }
175    return CollectionUtil.unmodifiableList(scopedConstraints);
176  }
177
178}