1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.metaschema.databind.io;
7   
8   import gov.nist.secauto.metaschema.core.configuration.IConfiguration;
9   import gov.nist.secauto.metaschema.core.configuration.IMutableConfiguration;
10  import gov.nist.secauto.metaschema.core.metapath.DynamicContext;
11  import gov.nist.secauto.metaschema.core.metapath.item.node.IDefinitionNodeItem;
12  import gov.nist.secauto.metaschema.core.metapath.item.node.IDocumentNodeItem;
13  import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem;
14  import gov.nist.secauto.metaschema.core.model.IBoundObject;
15  import gov.nist.secauto.metaschema.core.model.constraint.DefaultConstraintValidator;
16  import gov.nist.secauto.metaschema.core.model.constraint.IConstraintValidationHandler;
17  import gov.nist.secauto.metaschema.core.model.constraint.LoggingConstraintValidationHandler;
18  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
19  import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelAssembly;
20  
21  import java.io.IOException;
22  import java.io.Reader;
23  import java.net.URI;
24  
25  import edu.umd.cs.findbugs.annotations.NonNull;
26  
27  /**
28   * The base class of all format-specific deserializers.
29   *
30   * @param <CLASS>
31   *          the bound class to deserialize to
32   */
33  public abstract class AbstractDeserializer<CLASS extends IBoundObject>
34      extends AbstractSerializationBase<DeserializationFeature<?>>
35      implements IDeserializer<CLASS> {
36  
37    private IConstraintValidationHandler constraintValidationHandler;
38  
39    /**
40     * Construct a new deserializer.
41     *
42     * @param definition
43     *          the bound class information for the Java type this deserializer is
44     *          operating on
45     */
46    protected AbstractDeserializer(@NonNull IBoundDefinitionModelAssembly definition) {
47      super(definition);
48    }
49  
50    /**
51     * Get the constraint validation handler configured for this deserializer, which
52     * will be used to validate loaded data.
53     *
54     * @return the deserializer
55     */
56    @Override
57    @NonNull
58    public IConstraintValidationHandler getConstraintValidationHandler() {
59      synchronized (this) {
60        if (constraintValidationHandler == null) {
61          constraintValidationHandler = new LoggingConstraintValidationHandler();
62        }
63        return ObjectUtils.notNull(constraintValidationHandler);
64      }
65    }
66  
67    @Override
68    public void setConstraintValidationHandler(@NonNull IConstraintValidationHandler constraintValidationHandler) {
69      synchronized (this) {
70        this.constraintValidationHandler = constraintValidationHandler;
71      }
72    }
73  
74    @Override
75    public INodeItem deserializeToNodeItem(Reader reader, URI documentUri) throws IOException {
76  
77      INodeItem nodeItem;
78      try {
79        nodeItem = deserializeToNodeItemInternal(reader, documentUri);
80      } catch (Exception ex) { // NOPMD - this is intentional
81        throw new IOException(ex);
82      }
83  
84      if (isValidating()) {
85        validate(nodeItem);
86      }
87      return nodeItem;
88    }
89  
90    /**
91     * This abstract method delegates parsing to the concrete implementation.
92     *
93     * @param reader
94     *          the reader instance to read data from
95     * @param documentUri
96     *          the URI of the document that is being read
97     * @return a new node item containing the read contents
98     * @throws IOException
99     *           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 }