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  import java.util.concurrent.locks.Lock;
25  import java.util.concurrent.locks.ReentrantLock;
26  
27  import edu.umd.cs.findbugs.annotations.NonNull;
28  
29  /**
30   * The base class of all format-specific deserializers.
31   *
32   * @param <CLASS>
33   *          the bound class to deserialize to
34   */
35  public abstract class AbstractDeserializer<CLASS extends IBoundObject>
36      extends AbstractSerializationBase<DeserializationFeature<?>>
37      implements IDeserializer<CLASS> {
38  
39    private IConstraintValidationHandler constraintValidationHandler;
40    private final Lock handlerLock = new ReentrantLock();
41  
42    /**
43     * Construct a new deserializer.
44     *
45     * @param definition
46     *          the bound class information for the Java type this deserializer is
47     *          operating on
48     */
49    protected AbstractDeserializer(@NonNull IBoundDefinitionModelAssembly definition) {
50      super(definition);
51    }
52  
53    /**
54     * Get the constraint validation handler configured for this deserializer, which
55     * will be used to validate loaded data.
56     *
57     * @return the deserializer
58     */
59    @Override
60    @NonNull
61    public IConstraintValidationHandler getConstraintValidationHandler() {
62      handlerLock.lock();
63      try {
64        if (constraintValidationHandler == null) {
65          constraintValidationHandler = new LoggingConstraintValidationHandler();
66        }
67        return ObjectUtils.notNull(constraintValidationHandler);
68      } finally {
69        handlerLock.unlock();
70      }
71    }
72  
73    @Override
74    public void setConstraintValidationHandler(@NonNull IConstraintValidationHandler constraintValidationHandler) {
75      handlerLock.lock();
76      try {
77        this.constraintValidationHandler = constraintValidationHandler;
78      } finally {
79        handlerLock.unlock();
80      }
81    }
82  
83    @Override
84    public INodeItem deserializeToNodeItem(Reader reader, URI documentUri) throws IOException {
85  
86      INodeItem nodeItem;
87      try {
88        nodeItem = deserializeToNodeItemInternal(reader, documentUri);
89      } catch (Exception ex) { // NOPMD - this is intentional
90        throw new IOException(ex);
91      }
92  
93      if (isValidating()) {
94        validate(nodeItem);
95      }
96      return nodeItem;
97    }
98  
99    /**
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 }