001/* 002 * SPDX-FileCopyrightText: none 003 * SPDX-License-Identifier: CC0-1.0 004 */ 005 006package dev.metaschema.databind.io; 007 008import java.io.IOException; 009import java.io.Reader; 010import java.net.URI; 011import java.util.concurrent.locks.Lock; 012import java.util.concurrent.locks.ReentrantLock; 013 014import dev.metaschema.core.configuration.IConfiguration; 015import dev.metaschema.core.configuration.IMutableConfiguration; 016import dev.metaschema.core.metapath.DynamicContext; 017import dev.metaschema.core.metapath.item.node.IDefinitionNodeItem; 018import dev.metaschema.core.metapath.item.node.IDocumentNodeItem; 019import dev.metaschema.core.metapath.item.node.INodeItem; 020import dev.metaschema.core.model.IBoundObject; 021import dev.metaschema.core.model.constraint.ConstraintValidationException; 022import dev.metaschema.core.model.constraint.DefaultConstraintValidator; 023import dev.metaschema.core.model.constraint.IConstraintValidationHandler; 024import dev.metaschema.core.model.constraint.LoggingConstraintValidationHandler; 025import dev.metaschema.core.util.ObjectUtils; 026import dev.metaschema.databind.model.IBoundDefinitionModelAssembly; 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 try { 095 validate(nodeItem); 096 } catch (ConstraintValidationException ex) { 097 throw new IOException(ex); 098 } 099 } 100 return nodeItem; 101 } 102 103 /** 104 * This abstract method delegates parsing to the concrete implementation. 105 * 106 * @param reader 107 * the reader instance to read data from 108 * @param documentUri 109 * the URI of the document that is being read 110 * @return a new node item containing the read contents 111 * @throws IOException 112 * if an error occurred while reading data from the stream 113 */ 114 @NonNull 115 protected abstract INodeItem deserializeToNodeItemInternal(@NonNull Reader reader, @NonNull URI documentUri) 116 throws IOException; 117 118 @Override 119 public final CLASS deserializeToValue(Reader reader, URI documentUri) throws IOException { 120 CLASS retval; 121 122 if (isValidating()) { 123 INodeItem nodeItem = deserializeToNodeItemInternal(reader, documentUri); 124 try { 125 validate(nodeItem); 126 } catch (ConstraintValidationException ex) { 127 throw new IOException(ex); 128 } 129 retval = ObjectUtils.asType(ObjectUtils.requireNonNull(nodeItem.getValue())); 130 } else { 131 retval = deserializeToValueInternal(reader, documentUri); 132 } 133 return retval; 134 } 135 136 private void validate(@NonNull INodeItem nodeItem) throws ConstraintValidationException { 137 IDefinitionNodeItem<?, ?> definitionNodeItem; 138 if (nodeItem instanceof IDocumentNodeItem) { 139 definitionNodeItem = ((IDocumentNodeItem) nodeItem).getRootAssemblyNodeItem(); 140 } else if (nodeItem instanceof IDefinitionNodeItem) { 141 definitionNodeItem = (IDefinitionNodeItem<?, ?>) nodeItem; 142 } else { 143 throw new UnsupportedOperationException(String.format( 144 "The node item type '%s' is not supported for validation.", 145 nodeItem.getClass().getName())); 146 } 147 148 DynamicContext dynamicContext = new DynamicContext(nodeItem.getStaticContext()); 149 // Use permissive loader for documents referenced in constraint expressions 150 dynamicContext.setDocumentLoader(getBindingContext().newPermissiveBoundLoader()); 151 try (DefaultConstraintValidator validator = new DefaultConstraintValidator(getConstraintValidationHandler())) { 152 validator.validate(definitionNodeItem, dynamicContext); 153 validator.finalizeValidation(dynamicContext); 154 } 155 } 156 157 /** 158 * This abstract method delegates parsing to the concrete implementation, 159 * returning the deserialized value directly. 160 * 161 * @param reader 162 * the reader instance to read data from 163 * @param documentUri 164 * the URI of the document that is being read 165 * @return the deserialized object 166 * @throws IOException 167 * if an error occurred while reading data from the stream 168 */ 169 @NonNull 170 protected abstract CLASS deserializeToValueInternal(@NonNull Reader reader, @NonNull URI documentUri) 171 throws IOException; 172 173 @Override 174 public IDeserializer<CLASS> enableFeature(DeserializationFeature<?> feature) { 175 return set(feature, true); 176 } 177 178 @Override 179 public IDeserializer<CLASS> disableFeature(DeserializationFeature<?> feature) { 180 return set(feature, false); 181 } 182 183 @Override 184 public IDeserializer<CLASS> applyConfiguration( 185 @NonNull IConfiguration<DeserializationFeature<?>> other) { 186 IMutableConfiguration<DeserializationFeature<?>> config = getConfiguration(); 187 config.applyConfiguration(other); 188 configurationChanged(config); 189 return this; 190 } 191 192 @Override 193 public IDeserializer<CLASS> set(DeserializationFeature<?> feature, Object value) { 194 IMutableConfiguration<DeserializationFeature<?>> config = getConfiguration(); 195 config.set(feature, value); 196 configurationChanged(config); 197 return this; 198 } 199}