Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
mederly committed Mar 1, 2021
2 parents 0913080 + 364b4e1 commit 41f2bab
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 79 deletions.
Expand Up @@ -43,7 +43,7 @@ public class BeanMarshaller implements SchemaRegistry.InvalidationListener {

@NotNull private final PrismBeanInspector inspector;
@NotNull private final PrismContext prismContext;
@NotNull private final Map<Class,Marshaller> specialMarshallers = new HashMap<>();
@NotNull private final Map<Class<?>,Marshaller> specialMarshallers = new HashMap<>();

@NotNull private final Map<QName, Boolean> canProcessCache = new ConcurrentHashMap<>();

Expand Down Expand Up @@ -106,7 +106,7 @@ public XNodeImpl marshall(@Nullable Object inputBean, @Nullable SerializationCon
return (XNodeImpl) prismContext.xnodeSerializer().context(ctx).serializeRealValue(bean, new QName("dummy"))
.getSubnode();
} else if (bean instanceof Enum) {
return marshalEnum((Enum) bean, ctx);
return marshalEnum((Enum<?>) bean, ctx);
} else if (bean.getClass().getAnnotation(XmlType.class) != null) {
return marshalXmlType(bean, ctx);
} else if (bean instanceof Referencable) {
Expand Down Expand Up @@ -147,7 +147,7 @@ private XNodeImpl marshalXmlType(Object bean, SerializationContext ctx) throws S
if (ctd != null && ctd.isListMarker()) {
return marshalHeterogeneousList(bean, ctx);
} else {
return marshalXmlTypeToMap(bean, ctx);
return marshalXmlTypeToMap(bean, ctx, ctd);
}
}

Expand All @@ -158,13 +158,15 @@ private <T> PrimitiveXNodeImpl<T> marshallBeanToPrimitive(Object bean, Serializa
if (!valueField.isAccessible()) {
valueField.setAccessible(true);
}
T value;
Object value;
try {
value = (T) valueField.get(bean);
value = valueField.get(bean);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new SchemaException("Cannot get primitive value from field " + valueField.getName() + " of bean " + bean + ": "+e.getMessage(), e);
}
PrimitiveXNodeImpl<T> xnode = new PrimitiveXNodeImpl<>(value);

@SuppressWarnings("unchecked")
PrimitiveXNodeImpl<T> xnode = new PrimitiveXNodeImpl<>((T) value);
Class<?> fieldType = valueField.getType();
QName xsdType = XsdTypeMapper.toXsdType(fieldType);
xnode.setTypeQName(xsdType);
Expand All @@ -182,11 +184,11 @@ private XNodeImpl marshalHeterogeneousList(Object bean, SerializationContext ctx
+ " does not contain a collection but " + MiscUtil.getObjectName(getterResult));
}
ListXNodeImpl xlist = new ListXNodeImpl();
for (Object value : (Collection) getterResult) {
for (Object value : (Collection<?>) getterResult) {
if (!(value instanceof JAXBElement)) {
throw new IllegalStateException("Heterogeneous list contains a value that is not a JAXBElement: " + value);
}
JAXBElement jaxbElement = (JAXBElement) value;
JAXBElement<?> jaxbElement = (JAXBElement<?>) value;
Object realValue = jaxbElement.getValue();
if (realValue == null) {
throw new IllegalStateException("Heterogeneous list contains a null value"); // TODO
Expand All @@ -200,7 +202,7 @@ private XNodeImpl marshalHeterogeneousList(Object bean, SerializationContext ctx
return xlist;
}

private XNodeImpl marshalXmlTypeToMap(Object bean, SerializationContext ctx) throws SchemaException {
private XNodeImpl marshalXmlTypeToMap(Object bean, SerializationContext ctx, @Nullable ComplexTypeDefinition ctd) throws SchemaException {

Class<?> beanClass = bean.getClass();
MapXNodeImpl xmap;
Expand Down Expand Up @@ -233,70 +235,68 @@ private XNodeImpl marshalXmlTypeToMap(Object bean, SerializationContext ctx) thr
}

Field field = inspector.findPropertyField(beanClass, fieldName);
boolean isAttribute = inspector.isAttribute(field, getter);
Map.Entry<QName,XNodeImpl> marshalled = marshallField(getterResult, field, fieldName, getter, namespace, ctd, beanClass, ctx);
if (marshalled != null) {
xmap.put(marshalled.getKey(), marshalled.getValue());
}
}
return xmap;
}

private Map.Entry<QName, XNodeImpl> marshallField(Object getterResult,Field field, String fieldName, Method getter, String namespace, @Nullable ComplexTypeDefinition ctd, Class<?> beanClass, SerializationContext ctx) throws SchemaException {

boolean isAttribute = inspector.isAttribute(field, getter);
QName elementName = inspector.findFieldElementQName(fieldName, beanClass, namespace);
ItemDefinition<?> propDef = ctd != null ? ctd.findLocalItemDefinition(elementName) : null;

if (getterResult instanceof Collection<?>) {
Collection collection = (Collection) getterResult;
Collection<?> collection = (Collection<?>) getterResult;
if (collection.isEmpty()) {
continue;
return null;
}
Iterator i = collection.iterator();
Object getterResultValue = i.next();
Object getterResultValue = collection.iterator().next();
if (getterResultValue == null) {
continue;
return null;
}

// elementName will be determined from the first item on the list
// TODO make sure it will be correct with respect to other items as well!
if (getterResultValue instanceof JAXBElement && ((JAXBElement) getterResultValue).getName() != null) {
elementName = ((JAXBElement) getterResultValue).getName();
if (getterResultValue instanceof JAXBElement && ((JAXBElement<?>) getterResultValue).getName() != null) {
elementName = ((JAXBElement<?>) getterResultValue).getName();
// We should update propDef with substitution if possible
}

ListXNodeImpl xlist = new ListXNodeImpl();
for (Object value: collection) {
if (value == null) {
continue;
}
Object valueToMarshal = value;
if (value instanceof JAXBElement) {
valueToMarshal = ((JAXBElement) value).getValue();
}
QName typeName = inspector.findTypeName(field, valueToMarshal.getClass(), namespace);
// note: fieldTypeName is used only for attribute values here (when constructing PrimitiveXNode)
XNodeImpl marshaled = marshallValue(valueToMarshal, typeName, isAttribute, ctx);
setExplicitTypeDeclarationIfNeeded(marshaled, getter, valueToMarshal, typeName);
XNodeImpl marshaled = marshalSingleValue(value, field, fieldName, isAttribute, ctx, getter, propDef);
xlist.add(marshaled);
}
xmap.put(elementName, xlist);
} else {
QName fieldTypeName = inspector.findTypeName(field, getterResult.getClass(), namespace);
Object valueToMarshall;
if (getterResult instanceof JAXBElement) {
valueToMarshall = ((JAXBElement) getterResult).getValue();
elementName = ((JAXBElement) getterResult).getName();
} else {
valueToMarshall = getterResult;
}
XNodeImpl marshaled = marshallValue(valueToMarshall, fieldTypeName, isAttribute, ctx);
// TODO reconcile with setExplicitTypeDeclarationIfNeeded
if (!getter.getReturnType().equals(valueToMarshall.getClass()) && getter.getReturnType().isAssignableFrom(valueToMarshall.getClass()) && !(valueToMarshall instanceof Enum)) {
TypeDefinition def = prismContext.getSchemaRegistry().findTypeDefinitionByCompileTimeClass(valueToMarshall.getClass(), TypeDefinition.class);
if (def != null) {
QName type = def.getTypeName();
marshaled.setTypeQName(type);
marshaled.setExplicitTypeDeclaration(true);
}
}
if (marshaled != null) {
xmap.put(elementName, marshaled);
}

// setExplicitTypeDeclarationIfNeeded(getter, valueToMarshall, xmap, fieldTypeName);
return new AbstractMap.SimpleEntry<>(elementName, xlist);
}
}
// Value is not collection
if (getterResult instanceof JAXBElement) {
elementName = ((JAXBElement<?>) getterResult).getName();
}
XNodeImpl marshaled = marshalSingleValue(getterResult, field, fieldName, isAttribute, ctx, getter, propDef);
if (marshaled != null) {
return new AbstractMap.SimpleEntry<>(elementName, marshaled);
}
return null;
}

return xmap;
private XNodeImpl marshalSingleValue(Object value, Field field, String namespace, boolean isAttribute, SerializationContext ctx, Method getter, ItemDefinition<?> propDef) throws SchemaException {
Object valueToMarshal = value;
if (value instanceof JAXBElement) {
valueToMarshal = ((JAXBElement<?>) value).getValue();
}
QName typeName = inspector.findTypeName(field, valueToMarshal.getClass(), namespace);
// note: fieldTypeName is used only for attribute values here (when constructing PrimitiveXNode)
XNodeImpl marshaled = marshallValue(valueToMarshal, typeName, isAttribute, ctx);
updateExplicitType(marshaled, valueToMarshal.getClass(), typeName, unwrappedReturnType(getter), propDef);
return marshaled;
}

private Object getValue(Object bean, Method getter, String fieldOrPropertyName) {
Expand All @@ -309,19 +309,20 @@ private Object getValue(Object bean, Method getter, String fieldOrPropertyName)
return getterResult;
}

private XNodeImpl marshalEnum(Enum enumValue, SerializationContext ctx) {
Class<? extends Enum> enumClass = enumValue.getClass();
private XNodeImpl marshalEnum(Enum<?> enumValue, SerializationContext ctx) {
var enumClass = enumValue.getClass();
String enumStringValue = inspector.findEnumFieldValue(enumClass, enumValue.toString());
if (StringUtils.isEmpty(enumStringValue)){
enumStringValue = enumValue.toString();
}
QName fieldTypeName = inspector.findTypeName(null, enumClass, DEFAULT_PLACEHOLDER);
return createPrimitiveXNode(enumStringValue, fieldTypeName, false, ctx);

}

private XNodeImpl marshalXmlAsStringType(Object bean, SerializationContext ctx) {
private XNodeImpl marshalXmlAsStringType(Object bean, SerializationContext sc) {
PrimitiveXNodeImpl<String> xprim = new PrimitiveXNodeImpl<>();
xprim.setValue(((XmlAsStringType) bean).getContentAsString(), DOMUtil.XSD_STRING, getOptions(ctx));
xprim.setValue(((XmlAsStringType) bean).getContentAsString(), DOMUtil.XSD_STRING);
return xprim;
}

Expand Down Expand Up @@ -379,7 +380,7 @@ public void visit(Object bean, Handler<Object> handler) {
}

if (getterResult instanceof Collection<?>) {
Collection col = (Collection)getterResult;
Collection<?> col = (Collection<?>)getterResult;
if (col.isEmpty()) {
continue;
}
Expand All @@ -397,11 +398,63 @@ public void visit(Object bean, Handler<Object> handler) {
private void visitValue(Object element, Handler<Object> handler) {
Object elementToMarshall = element;
if (element instanceof JAXBElement){
elementToMarshall = ((JAXBElement) element).getValue();
elementToMarshall = ((JAXBElement<?>) element).getValue();
}
visit(elementToMarshall, handler);
}

private QName typeNameForClass(Class<?> jaxbClazz) {
// Primitive types
QName typeName = XsdTypeMapper.getJavaToXsdMapping(jaxbClazz);
if (typeName != null) {
return typeName;
}
return inspector.determineTypeForClass(jaxbClazz);
}

private void updateExplicitType(XNodeImpl marshaled, Class<? extends Object> valueClazz, QName valueType, Class<?> jaxbClazz,
ItemDefinition<?> propDef) {
if(marshaled == null || marshaled.isImmutable()) {
// RawType now return immutables
return;
}
if (valueType == null) {
// the idea is not to overwrite pre-existing explicit type declaration with null value
return;
}

if (marshaled.isExplicitTypeDeclaration()) {
// Explicit type already set
return;
}

if (marshaled.getTypeQName() == null && valueType != null) {
marshaled.setTypeQName(valueType);
}

QName schemaType = schemaTypeForClass(propDef, jaxbClazz);
if (Objects.equals(schemaType, valueType)) {
return;
}
if (DOMUtil.XSD_ANYTYPE.equals(schemaType)) {
// Field is anyType, lets record type to be safe.
marshaled.setExplicitTypeDeclaration(true);
}
if (!valueClazz.equals(jaxbClazz) && jaxbClazz.isAssignableFrom(valueClazz)) {
// It is subclass
marshaled.setExplicitTypeDeclaration(true);
}

}

private QName schemaTypeForClass(ItemDefinition<?> propDef, Class<?> jaxbClazz) {
QName schemaType = propDef != null ? propDef.getTypeName() : null;
if(schemaType != null) {
return schemaType;
}
return typeNameForClass(jaxbClazz);
}

private void setExplicitTypeDeclarationIfNeededForHeteroList(XNodeImpl node, Object realValue) {
QName elementName = node.getElementName();
QName typeName = inspector.determineTypeForClass(realValue.getClass());
Expand All @@ -412,36 +465,22 @@ && getSchemaRegistry().findTypeDefinitionByType(typeName, TypeDefinition.class)
}
}

// TODO shouldn't we use here the same approach as above?
private void setExplicitTypeDeclarationIfNeeded(XNodeImpl node, Method getter, Object getterResult, QName typeName) {
if (typeName == null) {
// the idea is not to overwrite pre-existing explicit type declaration with null value
// TODO reconsider
return;
}
Class getterReturnType = getter.getReturnType();
Class getterType = null;
if (Collection.class.isAssignableFrom(getterReturnType)) {
private Class<?> unwrappedReturnType(Method getter) {
Class<?> getterType = getter.getReturnType();
if (Collection.class.isAssignableFrom(getterType)) {
Type genericReturnType = getter.getGenericReturnType();
if (genericReturnType instanceof ParameterizedType) {
Type actualType = inspector.getTypeArgument(genericReturnType, "explicit type declaration");
if (actualType instanceof Class) {
getterType = (Class) actualType;
getterType = (Class<?>) actualType;
} else if (actualType instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) actualType;
Type typeArgument = inspector.getTypeArgument(parameterizedType, "JAXBElement return type");
getterType = inspector.getUpperBound(typeArgument, "JAXBElement return type");
}
}
}
if (getterType == null) {
getterType = getterReturnType;
}
Class getterResultReturnType = getterResult.getClass();
if (node != null && getterType != getterResultReturnType && getterType.isAssignableFrom(getterResultReturnType)) {
node.setExplicitTypeDeclaration(true);
node.setTypeQName(typeName);
}
return getterType;
}

// bean should have only two features: "list" attribute and multivalued property into which we should store the elements
Expand Down Expand Up @@ -560,7 +599,8 @@ private XNodeImpl marshalSchemaDefinition(Object o, SerializationContext ctx) {
}

// TODO create more appropriate interface to be able to simply serialize ProtectedStringType instances
public <T> MapXNodeImpl marshalProtectedDataType(Object o, SerializationContext ctx) throws SchemaException {
public <T> MapXNodeImpl marshalProtectedDataType(Object o, SerializationContext sc) throws SchemaException {
@SuppressWarnings("unchecked")
ProtectedDataType<T> protectedType = (ProtectedDataType<T>) o;
MapXNodeImpl xmap = new MapXNodeImpl();
if (protectedType.getEncryptedDataType() != null) {
Expand All @@ -573,7 +613,7 @@ public <T> MapXNodeImpl marshalProtectedDataType(Object o, SerializationContext
xmap.put(ProtectedDataType.F_HASHED_DATA, xHashedDataType);
} else if (protectedType.getClearValue() != null){
QName type = XsdTypeMapper.toXsdType(protectedType.getClearValue().getClass());
PrimitiveXNodeImpl xClearValue = createPrimitiveXNode(protectedType.getClearValue(), type, false, ctx);
PrimitiveXNodeImpl<?> xClearValue = createPrimitiveXNode(protectedType.getClearValue(), type, false, sc);
xmap.put(ProtectedDataType.F_CLEAR_VALUE, xClearValue);
}
// TODO: clearValue
Expand Down
@@ -0,0 +1,47 @@
package com.evolveum.midpoint.schema;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;

import javax.xml.namespace.QName;

import org.testng.annotations.Test;

import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.impl.xnode.PrimitiveXNodeImpl;
import com.evolveum.midpoint.prism.util.PrismTestUtil;
import com.evolveum.midpoint.prism.xnode.ListXNode;
import com.evolveum.midpoint.prism.xnode.MapXNode;
import com.evolveum.midpoint.prism.xnode.RootXNode;
import com.evolveum.midpoint.prism.xnode.XNode;
import com.evolveum.midpoint.util.DOMUtil;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.xml.ns._public.model.scripting_3.PipelineDataType;
import com.evolveum.midpoint.xml.ns._public.model.scripting_3.PipelineItemType;

public class TestBeanSerialization extends AbstractSchemaTest {



@Test
public void testPipelineItemXsdType() throws SchemaException {
PipelineDataType bean = new PipelineDataType();
bean.beginItem().value("s1");
PrismContext prismContext = PrismTestUtil.getPrismContext();
RootXNode xnode = prismContext.xnodeSerializer().root(new QName("output")).serializeRealValue(bean);
MapXNode output = (MapXNode) xnode.toMapXNode().get(new QName("output"));
XNode item = ((ListXNode) output.get(PipelineDataType.F_ITEM)).get(0);
assertTrue(item instanceof MapXNode);

XNode value = ((MapXNode) item).get(PipelineItemType.F_VALUE);
assertTrue(value instanceof PrimitiveXNodeImpl);

assertTrue(value.isExplicitTypeDeclaration());
assertEquals(value.getTypeQName(), DOMUtil.XSD_STRING);

//displayValue("output in XML", prismContext.xmlSerializer().root(new QName("output")).serializeRealValue(bean));


}

}

0 comments on commit 41f2bab

Please sign in to comment.