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