/* * Copyright 2017 requery.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.requery.jackson; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonStreamContext; import com.fasterxml.jackson.core.JsonTokenId; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.deser.BeanDeserializer; import com.fasterxml.jackson.databind.deser.BeanDeserializerBase; import com.fasterxml.jackson.databind.deser.SettableBeanProperty; import com.fasterxml.jackson.databind.deser.impl.ObjectIdReader; import com.fasterxml.jackson.databind.deser.impl.ReadableObjectId; import io.requery.util.ClassMap; import java.io.IOException; import java.lang.reflect.Method; /** * Attempts to read the id property from the json stream first, lookup the existing bean with * that id and serialize the content into that bean. Unfortunately doesn't seem to be possible * to do this with the current Jackson api so handle it ourselves. */ class EntityBeanDeserializer extends BeanDeserializer { private final ClassMap<Method> embeddedGetters = new ClassMap<>(); EntityBeanDeserializer(BeanDeserializerBase source, ObjectIdReader reader) { super(source, reader); } @Override public Object deserializeFromObject(JsonParser p, DeserializationContext ctxt) throws IOException { if (_nonStandardCreation || _needViewProcesing) { return super.deserializeFromObject(p, ctxt); } Object bean = null; if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)) { String propertyName = p.getCurrentName(); do { p.nextToken(); SettableBeanProperty property = _beanProperties.find(propertyName); if (property == null) { handleUnknownVanilla(p, ctxt, bean, propertyName); continue; } // lazily create the bean, the id property must be the first property if (bean == null) { if (propertyName.equals(_objectIdReader.propertyName.getSimpleName())) { // deserialize id Object id = property.deserialize(p, ctxt); ReadableObjectId objectId = ctxt.findObjectId(id, _objectIdReader.generator, _objectIdReader.resolver); bean = objectId == null ? null : objectId.resolve(); if (bean == null) { bean = _valueInstantiator.createUsingDefault(ctxt); property.set(bean, id); } } else { bean = _valueInstantiator.createUsingDefault(ctxt); } p.setCurrentValue(bean); } property.deserializeAndSet(p, ctxt, bean); } while ((propertyName = p.nextFieldName()) != null); } return bean; } @Override protected Object deserializeFromObjectUsingNonDefault(JsonParser p, DeserializationContext ctxt) throws IOException { JsonStreamContext parent = p.getParsingContext().getParent(); // handle embedded types if (parent != null && parent.getCurrentValue() != null) { Object value = parent.getCurrentValue(); Class<?> parentClass = value.getClass(); Method method = embeddedGetters.get(parentClass); if (method == null) { Class<?> target = getValueType().getRawClass(); for (Method m : parentClass.getDeclaredMethods()) { if (target.isAssignableFrom(m.getReturnType()) && m.getParameters().length == 0) { embeddedGetters.put(parentClass, m); method = m; break; } } } if (method != null) { try { return method.invoke(value); } catch (Exception e) { throw new RuntimeException(e); } } } return super.deserializeFromObjectUsingNonDefault(p, ctxt); } }