001/*
002 * SPDX-FileCopyrightText: none
003 * SPDX-License-Identifier: CC0-1.0
004 */
005
006package gov.nist.secauto.metaschema.databind.io;
007
008import gov.nist.secauto.metaschema.core.configuration.IConfiguration;
009import gov.nist.secauto.metaschema.core.configuration.IMutableConfiguration;
010import gov.nist.secauto.metaschema.core.metapath.DynamicContext;
011import gov.nist.secauto.metaschema.core.metapath.item.node.IDefinitionNodeItem;
012import gov.nist.secauto.metaschema.core.metapath.item.node.IDocumentNodeItem;
013import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem;
014import gov.nist.secauto.metaschema.core.model.IBoundObject;
015import gov.nist.secauto.metaschema.core.model.constraint.DefaultConstraintValidator;
016import gov.nist.secauto.metaschema.core.model.constraint.IConstraintValidationHandler;
017import gov.nist.secauto.metaschema.core.model.constraint.LoggingConstraintValidationHandler;
018import gov.nist.secauto.metaschema.core.util.ObjectUtils;
019import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelAssembly;
020
021import java.io.IOException;
022import java.io.Reader;
023import java.net.URI;
024
025import edu.umd.cs.findbugs.annotations.NonNull;
026
027/**
028 * The base class of all format-specific deserializers.
029 *
030 * @param <CLASS>
031 *          the bound class to deserialize to
032 */
033public abstract class AbstractDeserializer<CLASS extends IBoundObject>
034    extends AbstractSerializationBase<DeserializationFeature<?>>
035    implements IDeserializer<CLASS> {
036
037  private IConstraintValidationHandler constraintValidationHandler;
038
039  /**
040   * Construct a new deserializer.
041   *
042   * @param definition
043   *          the bound class information for the Java type this deserializer is
044   *          operating on
045   */
046  protected AbstractDeserializer(@NonNull IBoundDefinitionModelAssembly definition) {
047    super(definition);
048  }
049
050  /**
051   * Get the constraint validation handler configured for this deserializer, which
052   * will be used to validate loaded data.
053   *
054   * @return the deserializer
055   */
056  @Override
057  @NonNull
058  public IConstraintValidationHandler getConstraintValidationHandler() {
059    synchronized (this) {
060      if (constraintValidationHandler == null) {
061        constraintValidationHandler = new LoggingConstraintValidationHandler();
062      }
063      return ObjectUtils.notNull(constraintValidationHandler);
064    }
065  }
066
067  @Override
068  public void setConstraintValidationHandler(@NonNull IConstraintValidationHandler constraintValidationHandler) {
069    synchronized (this) {
070      this.constraintValidationHandler = constraintValidationHandler;
071    }
072  }
073
074  @Override
075  public INodeItem deserializeToNodeItem(Reader reader, URI documentUri) throws IOException {
076
077    INodeItem nodeItem;
078    try {
079      nodeItem = deserializeToNodeItemInternal(reader, documentUri);
080    } catch (Exception ex) { // NOPMD - this is intentional
081      throw new IOException(ex);
082    }
083
084    if (isValidating()) {
085      validate(nodeItem);
086    }
087    return nodeItem;
088  }
089
090  /**
091   * This abstract method delegates parsing to the concrete implementation.
092   *
093   * @param reader
094   *          the reader instance to read data from
095   * @param documentUri
096   *          the URI of the document that is being read
097   * @return a new node item containing the read contents
098   * @throws IOException
099   *           if an error occurred while reading data from the stream
100   */
101  @NonNull
102  protected abstract INodeItem deserializeToNodeItemInternal(@NonNull Reader reader, @NonNull URI documentUri)
103      throws IOException;
104
105  @Override
106  public final CLASS deserializeToValue(Reader reader, URI documentUri) throws IOException {
107    CLASS retval;
108
109    if (isValidating()) {
110      INodeItem nodeItem = deserializeToNodeItemInternal(reader, documentUri);
111      validate(nodeItem);
112      retval = ObjectUtils.asType(ObjectUtils.requireNonNull(nodeItem.getValue()));
113    } else {
114      retval = deserializeToValueInternal(reader, documentUri);
115    }
116    return retval;
117  }
118
119  private void validate(@NonNull INodeItem nodeItem) {
120    IDefinitionNodeItem<?, ?> definitionNodeItem;
121    if (nodeItem instanceof IDocumentNodeItem) {
122      definitionNodeItem = ((IDocumentNodeItem) nodeItem).getRootAssemblyNodeItem();
123    } else if (nodeItem instanceof IDefinitionNodeItem) {
124      definitionNodeItem = (IDefinitionNodeItem<?, ?>) nodeItem;
125    } else {
126      throw new UnsupportedOperationException(String.format(
127          "The node item type '%s' is not supported for validation.",
128          nodeItem.getClass().getName()));
129    }
130
131    DynamicContext dynamicContext = new DynamicContext(nodeItem.getStaticContext());
132    dynamicContext.setDocumentLoader(getBindingContext().newBoundLoader());
133    DefaultConstraintValidator validator = new DefaultConstraintValidator(getConstraintValidationHandler());
134    validator.validate(definitionNodeItem, dynamicContext);
135    validator.finalizeValidation(dynamicContext);
136  }
137
138  @NonNull
139  protected abstract CLASS deserializeToValueInternal(@NonNull Reader reader, @NonNull URI documentUri)
140      throws IOException;
141
142  @Override
143  public IDeserializer<CLASS> enableFeature(DeserializationFeature<?> feature) {
144    return set(feature, true);
145  }
146
147  @Override
148  public IDeserializer<CLASS> disableFeature(DeserializationFeature<?> feature) {
149    return set(feature, false);
150  }
151
152  @Override
153  public IDeserializer<CLASS> applyConfiguration(
154      @NonNull IConfiguration<DeserializationFeature<?>> other) {
155    IMutableConfiguration<DeserializationFeature<?>> config = getConfiguration();
156    config.applyConfiguration(other);
157    configurationChanged(config);
158    return this;
159  }
160
161  @Override
162  public IDeserializer<CLASS> set(DeserializationFeature<?> feature, Object value) {
163    IMutableConfiguration<DeserializationFeature<?>> config = getConfiguration();
164    config.set(feature, value);
165    configurationChanged(config);
166    return this;
167  }
168}