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