Skip to content

Commit

Permalink
[#1289] Fix issues with de-serializing of singular entity view attrib…
Browse files Browse the repository at this point in the history
…utes that use a collection type
  • Loading branch information
beikov committed Apr 22, 2021
1 parent 9025fbb commit 11dde29
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ None yet
* Retain entity view kind when converting entity view if possible
* Improve dialect detection for MariaDB dialects
* Workaround Hibernate proxy field access bug for non-pk relations
* Fix issues with de-serializing of singular entity view attributes that use a collection type

### Backwards-incompatible changes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

import com.blazebit.persistence.view.EntityViewManager;
import com.blazebit.persistence.view.metamodel.ManagedViewType;
import com.blazebit.persistence.view.metamodel.MethodAttribute;
import com.blazebit.persistence.view.metamodel.ViewMetamodel;
import com.blazebit.persistence.view.metamodel.ViewType;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.databind.BeanDescription;
Expand Down Expand Up @@ -97,14 +99,61 @@ public boolean isIsGetterVisible(AnnotatedMethod m) {

@Override
public boolean isSetterVisible(Method m) {
Class<?> rawParameterType;
return super.isSetterVisible(m) && !Collection.class.isAssignableFrom(rawParameterType = m.getParameterTypes()[0]) && !Map.class.isAssignableFrom(rawParameterType);
if (super.isSetterVisible(m)) {
Class<?> rawParameterType = m.getParameterTypes()[0];
if (Collection.class.isAssignableFrom(rawParameterType) || Map.class.isAssignableFrom(rawParameterType)) {
return isCollectionSetterVisible(m.getDeclaringClass(), m.getName());
} else {
return true;
}
}
return false;
}

@Override
public boolean isSetterVisible(AnnotatedMethod m) {
Class<?> rawParameterType;
return super.isSetterVisible(m) && !Collection.class.isAssignableFrom(rawParameterType = m.getRawParameterType(0)) && !Map.class.isAssignableFrom(rawParameterType);
if (super.isSetterVisible(m)) {
Class<?> rawParameterType = m.getRawParameterType(0);
if (Collection.class.isAssignableFrom(rawParameterType) || Map.class.isAssignableFrom(rawParameterType)) {
return isCollectionSetterVisible(m.getDeclaringClass(), m.getName());
} else {
return true;
}
}
return false;
}

private boolean isCollectionSetterVisible(Class<?> declaringClass, String setterName) {
final ViewMetamodel metamodel = entityViewManager.getMetamodel();
ManagedViewType<?> managedViewType = metamodel.managedView(declaringClass);
if (managedViewType == null) {
// This could be the implementation class, so check the super class
Class<?> superclass = declaringClass.getSuperclass();
if (superclass != Object.class) {
ManagedViewType<?> managedViewTypeSuper = metamodel.managedView(superclass);
if (managedViewTypeSuper != null) {
managedViewType = managedViewTypeSuper;
}
}
// If it is not, check the interfaces
if (managedViewType == null) {
for (Class<?> interfaceClass : declaringClass.getInterfaces()) {
ManagedViewType<?> managedViewTypeInterface = metamodel.managedView(interfaceClass);
if (managedViewTypeInterface != null) {
managedViewType = managedViewTypeInterface;
break;
}
}
}
// If this is not an entity view, the setter is visible
if (managedViewType == null) {
return true;
}
}
// If this is an entity view, the setter is only visible if this is a singular attribute
String attributeName = Character.toLowerCase(setterName.charAt(3)) + setterName.substring(4);
MethodAttribute<?, ?> attribute = managedViewType.getAttribute(attributeName);
return attribute == null || !attribute.isCollection();
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.blazebit.persistence.view.EntityView;
import com.blazebit.persistence.view.EntityViews;
import com.blazebit.persistence.view.IdMapping;
import com.blazebit.persistence.view.MappingSingular;
import com.blazebit.persistence.view.UpdatableEntityView;
import com.blazebit.persistence.view.spi.EntityViewConfiguration;
import com.blazebit.persistence.view.spi.type.EntityViewProxy;
Expand All @@ -39,6 +40,7 @@
import javax.persistence.Persistence;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import java.util.Set;

Expand Down Expand Up @@ -354,4 +356,28 @@ public void testJsonIgnore() throws Exception {
assertEquals(1L, viewAsJsonTree.get("id").asLong());
assertFalse(viewAsJsonTree.has("name"));
}

@EntityView(SomeEntity.class)
@CreatableEntityView
static interface ViewWithSingularCollection {
@IdMapping
long getId();
String getName();
void setName(String name);
@MappingSingular
List<String> getTags();
void setTags(List<String> tags);
}

@Test
public void testSingularCollection() throws Exception {
EntityViewAwareObjectMapper mapper = mapper(ViewWithSingularCollection.class);
ObjectReader objectReader = mapper.readerFor(mapper.getObjectMapper().constructType(ViewWithSingularCollection.class));
ViewWithSingularCollection view = objectReader.readValue("{\"name\": \"Joe\", \"tags\": [\"t1\", \"t2\"]}");
Assert.assertTrue(((EntityViewProxy) view).$$_isNew());
assertEquals("Joe", view.getName());
assertEquals(2, view.getTags().size());
assertEquals("t1", view.getTags().get(0));
assertEquals("t2", view.getTags().get(1));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@

package com.blazebit.persistence.integration.jackson;

import javax.persistence.Basic;
import javax.persistence.Convert;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import java.util.List;
import java.util.Set;

/**
Expand All @@ -34,6 +37,9 @@ public class SomeEntity {
String name;
@ManyToOne(fetch = FetchType.LAZY)
SomeEntity parent;
@Basic
@Convert(converter = StringListConverter.class)
List<String> tags;
@OneToMany(mappedBy = "parent")
Set<SomeEntity> children;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright 2014 - 2021 Blazebit.
*
* 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 com.blazebit.persistence.integration.jackson;

import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
* @author Christian Beikov
* @since 1.6.0
*/
@Converter
public class StringListConverter implements AttributeConverter<List<String>, String> {
@Override
public String convertToDatabaseColumn(List<String> value) {
if (value == null) {
return null;
}
StringBuilder sb = new StringBuilder();
int size = value.size();
sb.append(value.get(0));
for (int i = 1; i < size; i++) {
sb.append(',').append(value.get(i));
}
return sb.toString();
}

@Override
public List<String> convertToEntityAttribute(String dbData) {
if (dbData == null) {
return null;
}
return new ArrayList<>(Arrays.asList(dbData.split(",")));
}
}

0 comments on commit 11dde29

Please sign in to comment.