diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/marshaller/BeanMarshaller.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/marshaller/BeanMarshaller.java index b96b5993807..b98f8840ec3 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/marshaller/BeanMarshaller.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/marshaller/BeanMarshaller.java @@ -43,7 +43,7 @@ public class BeanMarshaller implements SchemaRegistry.InvalidationListener { @NotNull private final PrismBeanInspector inspector; @NotNull private final PrismContext prismContext; - @NotNull private final Map specialMarshallers = new HashMap<>(); + @NotNull private final Map,Marshaller> specialMarshallers = new HashMap<>(); @NotNull private final Map canProcessCache = new ConcurrentHashMap<>(); @@ -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) { @@ -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); } } @@ -158,13 +158,15 @@ private PrimitiveXNodeImpl 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 xnode = new PrimitiveXNodeImpl<>(value); + + @SuppressWarnings("unchecked") + PrimitiveXNodeImpl xnode = new PrimitiveXNodeImpl<>((T) value); Class fieldType = valueField.getType(); QName xsdType = XsdTypeMapper.toXsdType(fieldType); xnode.setTypeQName(xsdType); @@ -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 @@ -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; @@ -233,24 +235,35 @@ private XNodeImpl marshalXmlTypeToMap(Object bean, SerializationContext ctx) thr } Field field = inspector.findPropertyField(beanClass, fieldName); - boolean isAttribute = inspector.isAttribute(field, getter); + Map.Entry marshalled = marshallField(getterResult, field, fieldName, getter, namespace, ctd, beanClass, ctx); + if (marshalled != null) { + xmap.put(marshalled.getKey(), marshalled.getValue()); + } + } + return xmap; + } + private Map.Entry 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(); @@ -258,45 +271,32 @@ private XNodeImpl marshalXmlTypeToMap(Object bean, SerializationContext ctx) thr 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) { @@ -309,19 +309,20 @@ private Object getValue(Object bean, Method getter, String fieldOrPropertyName) return getterResult; } - private XNodeImpl marshalEnum(Enum enumValue, SerializationContext ctx) { - Class 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 xprim = new PrimitiveXNodeImpl<>(); - xprim.setValue(((XmlAsStringType) bean).getContentAsString(), DOMUtil.XSD_STRING, getOptions(ctx)); + xprim.setValue(((XmlAsStringType) bean).getContentAsString(), DOMUtil.XSD_STRING); return xprim; } @@ -379,7 +380,7 @@ public void visit(Object bean, Handler handler) { } if (getterResult instanceof Collection) { - Collection col = (Collection)getterResult; + Collection col = (Collection)getterResult; if (col.isEmpty()) { continue; } @@ -397,11 +398,63 @@ public void visit(Object bean, Handler handler) { private void visitValue(Object element, Handler 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 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()); @@ -412,21 +465,14 @@ && 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"); @@ -434,14 +480,7 @@ private void setExplicitTypeDeclarationIfNeeded(XNodeImpl node, Method getter, O } } } - 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 @@ -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 MapXNodeImpl marshalProtectedDataType(Object o, SerializationContext ctx) throws SchemaException { + public MapXNodeImpl marshalProtectedDataType(Object o, SerializationContext sc) throws SchemaException { + @SuppressWarnings("unchecked") ProtectedDataType protectedType = (ProtectedDataType) o; MapXNodeImpl xmap = new MapXNodeImpl(); if (protectedType.getEncryptedDataType() != null) { @@ -573,7 +613,7 @@ public 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 diff --git a/infra/schema/src/test/java/com/evolveum/midpoint/schema/TestBeanSerialization.java b/infra/schema/src/test/java/com/evolveum/midpoint/schema/TestBeanSerialization.java new file mode 100644 index 00000000000..9598631634b --- /dev/null +++ b/infra/schema/src/test/java/com/evolveum/midpoint/schema/TestBeanSerialization.java @@ -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)); + + + } + +} diff --git a/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/data/common/embedded/RFocusActivation.java b/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/data/common/embedded/RFocusActivation.java index b87be544c74..07529b01a50 100644 --- a/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/data/common/embedded/RFocusActivation.java +++ b/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/data/common/embedded/RFocusActivation.java @@ -6,6 +6,7 @@ */ package com.evolveum.midpoint.repo.sql.data.common.embedded; +import java.util.Objects; import javax.persistence.Embeddable; import com.evolveum.midpoint.prism.PrismContext; @@ -47,4 +48,19 @@ public static void copyToJAXB(RFocusActivation repo, ActivationType jaxb, PrismC jaxb.setLockoutStatus(repo.getLockoutStatus().getSchemaValue()); } } + + @Override + public boolean equals(Object o) { + if (this == o) { return true; } + if (o == null || getClass() != o.getClass()) { return false; } + if (!super.equals(o)) { return false; } + + RFocusActivation that = (RFocusActivation) o; + return lockoutStatus == that.lockoutStatus; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), lockoutStatus); + } }