/*
* 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 java.util.List;
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
import com.evolveum.midpoint.prism.*;
import com.evolveum.midpoint.prism.lex.dom.DomLexicalProcessor;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.w3c.dom.Element;
import com.evolveum.midpoint.prism.xml.XmlTypeConverter;
import com.evolveum.midpoint.prism.xml.XsdTypeMapper;
import com.evolveum.midpoint.prism.xnode.MapXNode;
import com.evolveum.midpoint.prism.xnode.PrimitiveXNode;
import com.evolveum.midpoint.prism.xnode.XNode;
import com.evolveum.midpoint.util.DOMUtil;
import com.evolveum.midpoint.util.JAXBUtil;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
/**
* A set of ugly hacks that are needed for prism and "real" JAXB to coexist. We hate it be we need it.
* This is a mix of DOM and JAXB code that allows the use of "any" methods on JAXB-generated objects.
* Prism normally does not use of of that. But JAXB code (such as JAX-WS) can invoke it and therefore
* it has to return correct DOM/JAXB elements as expected.
*
* @author Radovan Semancik
*/
public class JaxbDomHack {
private static final Trace LOGGER = TraceManager.getTrace(JaxbDomHack.class);
private PrismContext prismContext;
private DomLexicalProcessor domParser;
public JaxbDomHack(DomLexicalProcessor domParser, PrismContext prismContext) {
super();
this.domParser = domParser;
this.prismContext = prismContext;
}
private <T extends Containerable> ItemDefinition locateItemDefinition(
PrismContainerDefinition<T> containerDefinition, QName elementQName, Object valueElements)
throws SchemaException {
ItemDefinition def = containerDefinition.findItemDefinition(elementQName);
if (def != null) {
return def;
}
if (valueElements instanceof Element) {
// Try to locate xsi:type definition in the element
def = resolveDynamicItemDefinition(containerDefinition, elementQName, (Element) valueElements,
prismContext);
}
if (valueElements instanceof List){
List elements = (List) valueElements;
if (elements.size() == 1){
Object element = elements.get(0);
if (element instanceof JAXBElement){
Object val = ((JAXBElement) element).getValue();
if (val.getClass().isPrimitive()){
QName typeName = XsdTypeMapper.toXsdType(val.getClass());
PrismPropertyDefinitionImpl propDef = new PrismPropertyDefinitionImpl(elementQName, typeName, prismContext);
// propDef.setMaxOccurs(maxOccurs);
propDef.setDynamic(true);
return propDef;
}
}
}
}
if (def != null) {
return def;
}
if (containerDefinition.isRuntimeSchema()) {
// Try to locate global definition in any of the schemas
def = resolveGlobalItemDefinition(containerDefinition, elementQName);
}
return def;
}
private ItemDefinition resolveDynamicItemDefinition(ItemDefinition parentDefinition, QName elementName,
Element element, PrismContext prismContext) throws SchemaException {
QName typeName = null;
// QName elementName = null;
// Set it to multi-value to be on the safe side
int maxOccurs = -1;
// for (Object element : valueElements) {
// if (elementName == null) {
// elementName = JAXBUtil.getElementQName(element);
// }
// TODO: try JAXB types
if (element instanceof Element) {
Element domElement = (Element) element;
if (DOMUtil.hasXsiType(domElement)) {
typeName = DOMUtil.resolveXsiType(domElement);
if (typeName != null) {
String maxOccursString = domElement.getAttributeNS(
PrismConstants.A_MAX_OCCURS.getNamespaceURI(),
PrismConstants.A_MAX_OCCURS.getLocalPart());
if (!StringUtils.isBlank(maxOccursString)) {
// TODO
// maxOccurs = parseMultiplicity(maxOccursString, elementName);
}
// break;
}
}
}
// }
// FIXME: now the definition assumes property, may also be property
// container?
if (typeName == null) {
return null;
}
PrismPropertyDefinitionImpl propDef = new PrismPropertyDefinitionImpl(elementName, typeName, prismContext);
propDef.setMaxOccurs(maxOccurs);
propDef.setDynamic(true);
return propDef;
}
private <T extends Containerable> ItemDefinition resolveGlobalItemDefinition(
PrismContainerDefinition<T> containerDefinition, QName elementQName)
throws SchemaException {
// Object firstElement = valueElements.get(0);
// QName elementQName = JAXBUtil.getElementQName(firstElement);
return prismContext.getSchemaRegistry().resolveGlobalItemDefinition(elementQName, containerDefinition);
}
/**
* This is used in a form of "fromAny" to parse elements from a JAXB getAny method to prism.
*/
public <IV extends PrismValue,ID extends ItemDefinition,C extends Containerable> Item<IV,ID> parseRawElement(Object element, PrismContainerDefinition<C> definition) throws SchemaException {
Validate.notNull(definition, "Attempt to parse raw element in a container without definition");
QName elementName = JAXBUtil.getElementQName(element);
ItemDefinition itemDefinition = definition.findItemDefinition(elementName);
if (itemDefinition == null) {
itemDefinition = locateItemDefinition(definition, elementName, element);
if (itemDefinition == null) {
throw new SchemaException("No definition for item "+elementName);
}
}
PrismContext prismContext = definition.getPrismContext();
Item<IV,ID> subItem;
if (element instanceof Element) {
// DOM Element
subItem = prismContext.parserFor((Element) element).name(elementName).definition(itemDefinition).parseItem();
} else if (element instanceof JAXBElement<?>) {
// JAXB Element
JAXBElement<?> jaxbElement = (JAXBElement<?>)element;
Object jaxbBean = jaxbElement.getValue();
if (itemDefinition == null) {
throw new SchemaException("No definition for item "+elementName+" in container "+definition+" (parsed from raw element)", elementName);
}
if (itemDefinition instanceof PrismPropertyDefinition<?>) {
// property
PrismProperty<?> property = ((PrismPropertyDefinition<?>)itemDefinition).instantiate();
property.setRealValue(jaxbBean);
subItem = (Item<IV,ID>) property;
} else if (itemDefinition instanceof PrismContainerDefinition<?>) {
if (jaxbBean instanceof Containerable) {
PrismContainer<?> container = ((PrismContainerDefinition<?>)itemDefinition).instantiate();
PrismContainerValue subValue = ((Containerable)jaxbBean).asPrismContainerValue();
container.add(subValue);
subItem = (Item<IV,ID>) container;
} else {
throw new IllegalArgumentException("Unsupported JAXB bean "+jaxbBean.getClass());
}
} else if (itemDefinition instanceof PrismReferenceDefinition) {
// TODO
if (jaxbBean instanceof Referencable){
PrismReference reference = ((PrismReferenceDefinition)itemDefinition).instantiate();
PrismReferenceValue refValue = ((Referencable) jaxbBean).asReferenceValue();
reference.merge(refValue);
subItem = (Item<IV,ID>) reference;
} else{
throw new IllegalArgumentException("Unsupported JAXB bean" + jaxbBean);
}
} else {
throw new IllegalArgumentException("Unsupported definition type "+itemDefinition.getClass());
}
} else {
throw new IllegalArgumentException("Unsupported element type "+element.getClass());
}
return subItem;
}
/**
* Serializes prism value to JAXB "any" format as returned by JAXB getAny() methods.
*/
public Object toAny(PrismValue value) throws SchemaException {
if (value == null) {
return null;
}
Itemable parent = value.getParent();
if (parent == null) {
throw new IllegalStateException("Couldn't convert parent-less prism value to xsd:any: " + value);
}
QName elementName = parent.getElementName();
if (value instanceof PrismPropertyValue) {
PrismPropertyValue<Object> pval = (PrismPropertyValue)value;
if (pval.isRaw() && parent.getDefinition() == null) {
XNode rawElement = pval.getRawElement();
if (rawElement instanceof MapXNode) {
return domParser.serializeXMapToElement((MapXNode)rawElement, elementName);
} else if (rawElement instanceof PrimitiveXNode<?>) {
PrimitiveXNode<?> xprim = (PrimitiveXNode<?>)rawElement;
String stringValue = xprim.getStringValue();
Element element = DOMUtil.createElement(DOMUtil.getDocument(), elementName);
element.setTextContent(stringValue);
DOMUtil.setNamespaceDeclarations(element, xprim.getRelevantNamespaceDeclarations());
return element;
} else {
throw new IllegalArgumentException("Cannot convert raw element "+rawElement+" to xsd:any");
}
} else {
Object realValue = pval.getValue();
if (XmlTypeConverter.canConvert(realValue.getClass())) {
// Always record xsi:type. This is FIXME, but should work OK for now (until we put definition into deltas)
return XmlTypeConverter.toXsdElement(realValue, elementName, DOMUtil.getDocument(), true);
} else {
return wrapIfNeeded(realValue, elementName);
}
}
} else if (value instanceof PrismReferenceValue) {
return prismContext.domSerializer().serialize(value, elementName);
} else if (value instanceof PrismContainerValue<?>) {
PrismContainerValue<?> pval = (PrismContainerValue<?>)value;
if (pval.getParent().getCompileTimeClass() == null) {
// This has to be runtime schema without a compile-time representation.
// We need to convert it to DOM
return prismContext.domSerializer().serialize(pval, elementName);
} else {
return wrapIfNeeded(pval.asContainerable(), elementName);
}
} else {
throw new IllegalArgumentException("Unknown type "+value);
}
}
private Object wrapIfNeeded(Object value, QName elementName) {
if (value instanceof Element || value instanceof JAXBElement) {
return value;
} else {
return new JAXBElement(elementName, value.getClass(), value);
}
}
}