/* * Copyright (c) 2010-2016 Evolveum * * 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.evolveum.midpoint.prism.marshaller; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.polystring.PolyString; import com.evolveum.midpoint.prism.schema.SchemaRegistry; import com.evolveum.midpoint.prism.xml.XmlTypeConverter; import com.evolveum.midpoint.prism.xnode.*; import com.evolveum.midpoint.util.DOMUtil; import com.evolveum.midpoint.util.JAXBUtil; import com.evolveum.midpoint.util.QNameUtil; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.prism.xml.ns._public.query_3.SearchFilterType; import com.evolveum.prism.xml.ns._public.types_3.EvaluationTimeType; import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.Validate; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.xml.namespace.QName; import java.util.ArrayList; import java.util.Collection; /** * @author semancik * */ public class PrismMarshaller { @NotNull private final BeanMarshaller beanMarshaller; public PrismMarshaller(@NotNull BeanMarshaller beanMarshaller) { this.beanMarshaller = beanMarshaller; } //region Public interface ====================================================================================== /* * These methods should not be called from the inside of marshaller. At entry, they invoke ItemInfo methods * to determine item name, definition and type to use. From inside marshalling process, these elements have * to be provided more-or-less by the caller. */ /** * Marshals a given prism item (object, container, reference, property). * * @param item Item to be marshaled. * @param itemName Name to give to the item in the marshaled form. Usually null (i.e. taken from the item itself). * @param itemDefinition Definition to be used when parsing. Usually null (i.e. taken from the item itself). * @param context Serialization context. * @return Marshaled item. */ @NotNull RootXNode marshalItemAsRoot(@NotNull Item<?, ?> item, QName itemName, ItemDefinition itemDefinition, SerializationContext context) throws SchemaException { @NotNull QName realItemName = itemName != null ? itemName : item.getElementName(); ItemDefinition realItemDefinition = itemDefinition != null ? itemDefinition : item.getDefinition(); XNode content = marshalItemContent(item, realItemDefinition, context); if (realItemDefinition != null) { addTypeDefinitionIfNeeded(realItemName, realItemDefinition.getTypeName(), content); } return new RootXNode(realItemName, content); } /** * Marshals a single PrismValue. For simplicity and compatibility with other interfaces, the result is always a RootXNode. * * @param value PrismValue to be marshaled. * @param itemName Item name to be used. Optional. If omitted, it is derived from value and/or definition. * @param itemDefinition Item definition to be used. Optional. * @param context Serialization context. * @return Marshaled prism value. */ @NotNull RootXNode marshalPrismValueAsRoot(@NotNull PrismValue value, QName itemName, ItemDefinition itemDefinition, SerializationContext context) throws SchemaException { ItemInfo itemInfo = ItemInfo.determineFromValue(value, itemName, itemDefinition, getSchemaRegistry()); QName realItemName = itemInfo.getItemName(); ItemDefinition realItemDefinition = itemInfo.getItemDefinition(); QName realItemTypeName = itemInfo.getTypeName(); if (realItemName == null) { throw new IllegalArgumentException("Couldn't determine item name from the prism value; cannot marshal to RootXNode"); } XNode valueNode = marshalItemValue(value, realItemDefinition, realItemTypeName, context); addTypeDefinitionIfNeeded(realItemName, realItemTypeName, valueNode); return new RootXNode(realItemName, valueNode); } /** * Marshals any data - prism item or real value. * * @param object Object to be marshaled. * @param itemName Item name to be used. Optional. If omitted, it is derived from value and/or definition. * @param itemDefinition Item definition to be used. Optional. * @param context Serialization context. * @return Marshaled object. */ @NotNull RootXNode marshalAnyData(@NotNull Object object, QName itemName, ItemDefinition itemDefinition, SerializationContext context) throws SchemaException { if (object instanceof Item) { return marshalItemAsRoot((Item) object, itemName, itemDefinition, context); } Validate.notNull(itemName, "itemName must be specified for non-Item objects"); if (object instanceof Containerable) { return marshalPrismValueAsRoot(((Containerable) object).asPrismContainerValue(), itemName, null, context); } else if (beanMarshaller.canProcess(object.getClass())) { XNode valueNode = beanMarshaller.marshall(object, context); // TODO item definition! QName typeName = JAXBUtil.getTypeQName(object.getClass()); if (valueNode != null) { addTypeDefinitionIfNeeded(itemName, typeName, valueNode); if (valueNode.getTypeQName() == null && typeName == null) { throw new SchemaException("No type QName for class " + object.getClass()); } } // TODO or else put type name at least to root? (but, can valueNode be null if object is not null?) return new RootXNode(itemName, valueNode); } else { throw new IllegalArgumentException("Couldn't serialize " + object); } } public boolean canSerialize(Object object) { if (object instanceof Item) { return true; } else { return beanMarshaller.canProcess(object.getClass()); } } /* * TODO reconsider what to return for empty items * 1. null * 2. Root(name, null) * 3. Root(name, List()) * * TODO reconsider what to do if we have potentially multivalued property - whether to return list or not */ //endregion //region Implementation ====================================================================================== /** * Marshals everything from the item except for the root node. * Separated from marshalItemAsRoot in order to be reusable. */ @NotNull private XNode marshalItemContent(@NotNull Item<?, ?> item, ItemDefinition itemDefinition, SerializationContext context) throws SchemaException { if (item.size() == 1) { return marshalItemValue(item.getValue(0), itemDefinition, null, context); } else { ListXNode xlist = new ListXNode(); for (PrismValue val : item.getValues()) { xlist.add(marshalItemValue(val, itemDefinition, null, context)); } return xlist; } } @NotNull private <O extends Objectable> MapXNode marshalObjectContent(@NotNull PrismObject<O> object, @NotNull PrismObjectDefinition<O> objectDefinition, SerializationContext ctx) throws SchemaException { MapXNode xmap = new MapXNode(); marshalContainerValue(xmap, object.getValue(), objectDefinition, ctx); xmap.setTypeQName(objectDefinition.getTypeName()); // object should have the definition (?) return xmap; } @SuppressWarnings("unchecked") @NotNull private XNode marshalItemValue(@NotNull PrismValue itemValue, @Nullable ItemDefinition definition, @Nullable QName typeName, SerializationContext ctx) throws SchemaException { XNode xnode; if (definition == null && typeName == null && itemValue instanceof PrismPropertyValue) { return serializePropertyRawValue((PrismPropertyValue<?>) itemValue); } else if (itemValue instanceof PrismReferenceValue) { xnode = serializeReferenceValue((PrismReferenceValue)itemValue, (PrismReferenceDefinition) definition, ctx); } else if (itemValue instanceof PrismPropertyValue<?>) { xnode = serializePropertyValue((PrismPropertyValue<?>)itemValue, (PrismPropertyDefinition) definition, typeName); } else if (itemValue instanceof PrismContainerValue<?>) { xnode = marshalContainerValue((PrismContainerValue<?>)itemValue, (PrismContainerDefinition) definition, ctx); } else { throw new IllegalArgumentException("Unsupported value type "+itemValue.getClass()); } if (definition != null && definition.isDynamic() && isInstantiable(definition)) { if (xnode.getTypeQName() == null) { xnode.setTypeQName(definition.getTypeName()); } xnode.setExplicitTypeDeclaration(true); } return xnode; } // TODO FIXME first of all, Extension definition should not be marked as dynamic private boolean isInstantiable(ItemDefinition definition) { if (definition.isAbstract()) { return false; } if (definition instanceof PrismContainerDefinition) { PrismContainerDefinition pcd = (PrismContainerDefinition) definition; ComplexTypeDefinition ctd = pcd.getComplexTypeDefinition(); return ctd != null && ctd.getCompileTimeClass() != null; } else if (definition instanceof PrismPropertyDefinition) { PrismPropertyDefinition ppd = (PrismPropertyDefinition) definition; if (ppd.isAnyType()) { return false; } // TODO optimize return getSchemaRegistry().determineClassForType(ppd.getTypeName()) != null || getSchemaRegistry().findTypeDefinitionByType(ppd.getTypeName(), TypeDefinition.class) != null; } else { return false; } } private <C extends Containerable> MapXNode marshalContainerValue(PrismContainerValue<C> containerVal, PrismContainerDefinition<C> containerDefinition, SerializationContext ctx) throws SchemaException { MapXNode xmap = new MapXNode(); marshalContainerValue(xmap, containerVal, containerDefinition, ctx); return xmap; } private <C extends Containerable> void marshalContainerValue(MapXNode xmap, PrismContainerValue<C> containerVal, PrismContainerDefinition<C> containerDefinition, SerializationContext ctx) throws SchemaException { Long id = containerVal.getId(); if (id != null) { xmap.put(XNode.KEY_CONTAINER_ID, createPrimitiveXNodeAttr(id, DOMUtil.XSD_LONG)); } if (containerVal instanceof PrismObjectValue) { PrismObjectValue<?> objectVal = (PrismObjectValue<?>) containerVal; if (objectVal.getOid() != null) { xmap.put(XNode.KEY_OID, createPrimitiveXNodeStringAttr(objectVal.getOid())); } if (objectVal.getVersion() != null) { xmap.put(XNode.KEY_VERSION, createPrimitiveXNodeStringAttr(objectVal.getVersion())); } } // We put the explicit type name only if it's different from the parent one // (assuming this value is NOT serialized as a standalone one: in that case its // type must be marshaled in a special way). QName specificTypeName = getSpecificTypeName(containerVal); if (specificTypeName != null) { xmap.setTypeQName(specificTypeName); xmap.setExplicitTypeDeclaration(true); } Collection<QName> marshaledItems = new ArrayList<>(); if (containerDefinition != null) { // We have to serialize in the definition order. Some data formats (XML) are // ordering-sensitive. We need to keep that ordering otherwise the resulting // document won't pass schema validation for (ItemDefinition itemDef: containerDefinition.getDefinitions()) { QName elementName = itemDef.getName(); Item<?,?> item = containerVal.findItem(elementName); if (item != null) { XNode xsubnode = marshalItemContent(item, getItemDefinition(containerVal, item), ctx); xmap.put(elementName, xsubnode); marshaledItems.add(elementName); } } } // There are some cases when we do not have list of all elements in a container. // E.g. in run-time schema. Therefore we must also iterate over items and not just item definitions. if (containerVal.getItems() != null){ for (Item<?,?> item : containerVal.getItems()) { QName elementName = item.getElementName(); if (marshaledItems.contains(elementName)) { continue; } XNode xsubnode = marshalItemContent(item, getItemDefinition(containerVal, item), ctx); xmap.put(elementName, xsubnode); } } } private <C extends Containerable> ItemDefinition getItemDefinition(PrismContainerValue<C> cval, Item<?, ?> item) { if (item.getDefinition() != null) { return item.getDefinition(); } ComplexTypeDefinition ctd = cval.getComplexTypeDefinition(); if (ctd == null) { return null; } return ctd.findItemDefinition(item.getElementName()); } // Returns type QName if it is different from parent's one and if it's suitable to be put to marshaled form private <C extends Containerable> QName getSpecificTypeName(PrismContainerValue<C> cval) { if (cval.getParent() == null) { return null; } ComplexTypeDefinition ctdValue = cval.getComplexTypeDefinition(); ComplexTypeDefinition ctdParent = cval.getParent().getComplexTypeDefinition(); QName typeValue = ctdValue != null ? ctdValue.getTypeName() : null; QName typeParent = ctdParent != null ? ctdParent.getTypeName() : null; if (typeValue == null || typeValue.equals(typeParent)) { return null; } if (ctdValue.getCompileTimeClass() == null) { // TODO................. return null; } return typeValue; } private XNode serializeReferenceValue(PrismReferenceValue value, PrismReferenceDefinition definition, SerializationContext ctx) throws SchemaException { MapXNode xmap = new MapXNode(); boolean containsOid = false; String namespace = definition != null ? definition.getNamespace() : null; // namespace for filter and description if (StringUtils.isNotBlank(value.getOid())){ containsOid = true; xmap.put(XNode.KEY_REFERENCE_OID, createPrimitiveXNodeStringAttr(value.getOid())); } QName relation = value.getRelation(); if (relation != null) { xmap.put(XNode.KEY_REFERENCE_RELATION, createPrimitiveXNodeAttr(relation, DOMUtil.XSD_QNAME)); } QName targetType = value.getTargetType(); if (targetType != null) { xmap.put(XNode.KEY_REFERENCE_TYPE, createPrimitiveXNodeAttr(targetType, DOMUtil.XSD_QNAME)); } String description = value.getDescription(); if (description != null) { xmap.put(createReferenceQName(XNode.KEY_REFERENCE_DESCRIPTION, namespace), createPrimitiveXNode(description, DOMUtil.XSD_STRING)); } SearchFilterType filter = value.getFilter(); if (filter != null) { XNode xsubnode = filter.serializeToXNode(); if (xsubnode != null) { xmap.put(createReferenceQName(XNode.KEY_REFERENCE_FILTER, namespace), xsubnode); } } EvaluationTimeType resolutionTime = value.getResolutionTime(); if (resolutionTime != null) { xmap.put(createReferenceQName(XNode.KEY_REFERENCE_RESOLUTION_TIME, namespace), createPrimitiveXNode(resolutionTime.value(), DOMUtil.XSD_STRING)); } if (value.getTargetName() != null) { if (SerializationContext.isSerializeReferenceNames(ctx)) { XNode xsubnode = createPrimitiveXNode(value.getTargetName(), PolyStringType.COMPLEX_TYPE); xmap.put(createReferenceQName(XNode.KEY_REFERENCE_TARGET_NAME, namespace), xsubnode); } else { String commentValue = " " + value.getTargetName().getOrig() + " "; xmap.setComment(commentValue); } } boolean isComposite = false; if (definition != null) { isComposite = definition.isComposite(); } if ((SerializationContext.isSerializeCompositeObjects(ctx) || isComposite || !containsOid) && value.getObject() != null) { XNode xobjnode = marshalObjectContent(value.getObject(), value.getObject().getDefinition(), ctx); xmap.put(createReferenceQName(XNode.KEY_REFERENCE_OBJECT, namespace), xobjnode); } return xmap; } // expects that qnames have null namespaces by default // namespace (second parameter) may be null if unknown private QName createReferenceQName(QName qname, String namespace) { if (namespace != null) { return new QName(namespace, qname.getLocalPart()); } else { return qname; } } //endregion //region Serializing properties - specific functionality private <T> XNode serializePropertyValue(@NotNull PrismPropertyValue<T> value, PrismPropertyDefinition<T> definition, QName typeNameIfNoDefinition) throws SchemaException { @Nullable QName typeName = definition != null ? definition.getTypeName() : typeNameIfNoDefinition; T realValue = value.getValue(); if (realValue instanceof PolyString) { return serializePolyString((PolyString) realValue); } else if (beanMarshaller.canProcess(typeName)) { XNode xnode = beanMarshaller.marshall(realValue); if (realValue.getClass().getPackage() != null) { TypeDefinition typeDef = getSchemaRegistry() .findTypeDefinitionByCompileTimeClass(realValue.getClass(), TypeDefinition.class); if (xnode != null && typeDef != null && !QNameUtil.match(typeDef.getTypeName(), typeName)) { xnode.setTypeQName(typeDef.getTypeName()); xnode.setExplicitTypeDeclaration(true); } } return xnode; } else { // primitive value return createPrimitiveXNode(realValue, typeName); } } private XNode serializePolyString(PolyString realValue) { PrimitiveXNode<PolyString> xprim = new PrimitiveXNode<>(); xprim.setValue(realValue, PolyStringType.COMPLEX_TYPE); return xprim; } @NotNull private <T> XNode serializePropertyRawValue(PrismPropertyValue<T> value) throws SchemaException { XNode rawElement = value.getRawElement(); if (rawElement != null) { return rawElement; } T realValue = value.getValue(); if (realValue != null) { return createPrimitiveXNode(realValue, null); } else { throw new IllegalStateException("Neither real nor raw value present in " + value); } } private PrimitiveXNode<String> createPrimitiveXNodeStringAttr(String val) { return createPrimitiveXNodeAttr(val, DOMUtil.XSD_STRING); } private <T> PrimitiveXNode<T> createPrimitiveXNodeAttr(T val, QName type) { PrimitiveXNode<T> xprim = createPrimitiveXNode(val, type); xprim.setAttribute(true); return xprim; } @NotNull private <T> PrimitiveXNode<T> createPrimitiveXNode(T val, QName type) { PrimitiveXNode<T> xprim = new PrimitiveXNode<T>(); xprim.setValue(val, type); return xprim; } @NotNull private SchemaRegistry getSchemaRegistry() { return beanMarshaller.getPrismContext().getSchemaRegistry(); } private void addTypeDefinitionIfNeeded(@NotNull QName itemName, QName typeName, @NotNull XNode valueNode) { if (valueNode.getTypeQName() != null && valueNode.isExplicitTypeDeclaration()) { return; // already set } if (typeName == null) { return; // nothing to do, anyway } if (!getSchemaRegistry().hasImplicitTypeDefinition(itemName, typeName) && (XmlTypeConverter.canConvert(typeName) || getSchemaRegistry().findTypeDefinitionByType(typeName) != null)) { valueNode.setTypeQName(typeName); valueNode.setExplicitTypeDeclaration(true); } } //endregion }