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