/*
* Copyright (c) 2010-2014 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.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.namespace.QName;
import com.evolveum.midpoint.prism.Containerable;
import com.evolveum.midpoint.prism.PrismContainer;
import com.evolveum.midpoint.prism.PrismContainerDefinition;
import com.evolveum.midpoint.prism.schema.SchemaRegistryImpl;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.Validate;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import com.evolveum.midpoint.prism.Objectable;
import com.evolveum.midpoint.prism.PrismContainerValue;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.PrismObjectDefinition;
import com.evolveum.midpoint.prism.schema.SchemaDescription;
import com.evolveum.midpoint.prism.schema.SchemaRegistry;
import com.evolveum.midpoint.prism.xml.DynamicNamespacePrefixMapper;
import com.evolveum.midpoint.util.DOMUtil;
import com.evolveum.midpoint.util.JAXBUtil;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.exception.SystemException;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
/**
* DEPRECATED as of May 12, 2014. MidPoint data structures are not to be processed by JAXB any more.
* (The last drop was the inability to hide RawType from externally-visible model service WSDL.
* The solution devised, based on @Raw type, prevents JAXB from correctly parsing any structures that
* contain RawType elements.)
*
* So, don't use JaxbTestUtil even for testing purposes. Use PrismTestUtil instead.
*
* ===
* Original description:
*
* JAXB testing util. Only for use in tests. DO NOT USE IN PRODUCTION CODE.
* This util is used to test the ablility of prism JAXB representation to be used by
* native (Sun) JAXB code.
*
* Note: this is what used to be PrismJaxbProcessor. Therefore there may be still a lot of junk to clean up.
*
* @author Radovan Semancik
*/
@Deprecated
public class JaxbTestUtil {
private static final QName DEFAULT_ELEMENT_NAME = new QName("http://midpoint.evolveum.com/xml/ns/test/whatever-1.xsd", "whatever");
private static final Trace LOGGER = TraceManager.getTrace(JaxbTestUtil.class);
private PrismContext prismContext;
private JAXBContext context;
private static JaxbTestUtil instance;
public static JaxbTestUtil getInstance() {
if (instance == null) {
instance = new JaxbTestUtil();
instance.prismContext = PrismTestUtil.getPrismContext();
instance.initialize();
}
return instance;
}
private JaxbTestUtil() {
}
public PrismContext getPrismContext() {
return prismContext;
}
private SchemaRegistry getSchemaRegistry() {
return prismContext.getSchemaRegistry();
}
public void initialize() {
StringBuilder sb = new StringBuilder();
Iterator<Package> iterator = getSchemaRegistry().getCompileTimePackages().iterator();
while (iterator.hasNext()) {
Package jaxbPackage = iterator.next();
sb.append(jaxbPackage.getName());
if (iterator.hasNext()) {
sb.append(":");
}
}
String jaxbPaths = sb.toString();
if (jaxbPaths.isEmpty()) {
LOGGER.debug("No JAXB paths, skipping creation of JAXB context");
} else {
try {
context = JAXBContext.newInstance(jaxbPaths);
} catch (JAXBException ex) {
throw new SystemException("Couldn't create JAXBContext for: " + jaxbPaths, ex);
}
}
}
public JAXBContext getContext() {
return context;
}
public void setContext(JAXBContext context) {
this.context = context;
}
public boolean isJaxbClass(Class<?> clazz) {
if (clazz == null) {
throw new IllegalArgumentException("No class, no fun");
}
if (clazz.getPackage() == null) {
// No package: this is most likely a primitive type and definitely
// not a JAXB class
return false;
}
for (Package jaxbPackage: getSchemaRegistry().getCompileTimePackages()) {
if (jaxbPackage.equals(clazz.getPackage())) {
return true;
}
}
return false;
}
public boolean canConvert(Class<?> clazz) {
return isJaxbClass(clazz);
}
public boolean canConvert(QName xsdType) {
SchemaDescription schemaDesc = getSchemaRegistry().findSchemaDescriptionByNamespace(xsdType.getNamespaceURI());
if (schemaDesc == null) {
return false;
}
if (schemaDesc.getCompileTimeClassesPackage() == null) {
return false;
}
// We may be answering "yes" to a broader set of types that we can really convert.
// But that does not matter that much. If the type is in the correct namespace
// then either we can convert it or nobody can.
return true;
// Following code is not really correct. There are XSD types that we can convert and there is
// no complexTypeDefinition for then in our parsed schema. E.g. all the property JAXB types.
// ComplexTypeDefinition complexTypeDefinition = schema.findComplexTypeDefinition(xsdType);
// return complexTypeDefinition != null;
}
public <T> Class<T> getCompileTimeClass(QName xsdType) {
SchemaDescription desc = getSchemaRegistry().findSchemaDescriptionByNamespace(xsdType.getNamespaceURI());
if (desc == null) {
return null;
}
Map<QName, Class<?>> map = desc.getXsdTypeTocompileTimeClassMap();
if (map == null) {
return null;
}
return (Class<T>) map.get(xsdType);
}
public <T> T toJavaValue(Element element, Class<T> typeClass) throws JAXBException {
QName type = JAXBUtil.getTypeQName(typeClass);
return (T) toJavaValue(element, type);
}
/**
* Used to convert property values from DOM
*/
public Object toJavaValue(Element element, QName xsdType) throws JAXBException {
Class<?> declaredType = getCompileTimeClass(xsdType);
if (declaredType == null) {
// This may happen if the schema is runtime and there is no associated compile-time class
throw new SystemException("Cannot determine Java type for "+xsdType);
}
JAXBElement<?> jaxbElement = createUnmarshaller().unmarshal(element, declaredType);
Object object = jaxbElement.getValue();
return object;
}
private Marshaller createMarshaller(Map<String, Object> jaxbProperties) throws JAXBException {
Marshaller marshaller = context.createMarshaller();
// set default properties
marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
DynamicNamespacePrefixMapper namespacePrefixMapper = ((SchemaRegistryImpl) getSchemaRegistry()).getNamespacePrefixMapper().clone();
namespacePrefixMapper.setAlwaysExplicit(true);
marshaller.setProperty("com.sun.xml.bind.namespacePrefixMapper", namespacePrefixMapper);
// set custom properties
if (jaxbProperties != null) {
for (Entry<String, Object> property : jaxbProperties.entrySet()) {
marshaller.setProperty(property.getKey(), property.getValue());
}
}
return marshaller;
}
/**
* Allow for pooling and other fancy stuff. Now it dumb, creates in every call.
*/
private Marshaller getMarshaller() throws JAXBException {
return createMarshaller(null);
}
private Unmarshaller createUnmarshaller() throws JAXBException {
return context.createUnmarshaller();
}
/**
* Allow for pooling and other fancy stuff. Now it dumb, creates in every call.
*/
public Unmarshaller getUnmarshaller() throws JAXBException {
return createUnmarshaller();
}
public String marshalToString(Objectable objectable) throws JAXBException {
return marshalToString(objectable, new HashMap<String, Object>());
}
public String marshalToString(Objectable objectable, Map<String, Object> properties) throws JAXBException {
QName elementQName = determineElementQName(objectable);
JAXBElement<Object> jaxbElement = new JAXBElement<Object>(elementQName, (Class) objectable.getClass(), objectable);
return marshalElementToString(jaxbElement, properties);
}
public String marshalElementToString(JAXBElement<?> jaxbElement) throws JAXBException {
return marshalElementToString(jaxbElement, new HashMap<String, Object>());
}
public String marshalElementToString(JAXBElement<?> jaxbElement, Map<String, Object> properties) throws JAXBException {
StringWriter writer = new StringWriter();
Marshaller marshaller = getMarshaller();
for (Entry<String, Object> entry : properties.entrySet()) {
marshaller.setProperty(entry.getKey(), entry.getValue());
}
marshaller.marshal(jaxbElement, writer);
return writer.getBuffer().toString();
}
public String marshalElementToString(Object element) throws JAXBException {
return marshalElementToString(element, new HashMap<String, Object>());
}
/**
* Serializes DOM or JAXB element to string
*/
public String marshalElementToString(Object element, Map<String, Object> properties) throws JAXBException {
if (element == null) {
return null;
}
if (element instanceof Element) {
return DOMUtil.serializeDOMToString((Element) element);
} else if (element instanceof JAXBElement) {
return marshalElementToString((JAXBElement<?>)element, properties);
} else {
throw new IllegalArgumentException("Unsupported element type "+element.getClass().getName());
}
}
/**
* Serializes DOM or JAXB element to string, using specified elementName if needed.
*/
public String marshalElementToString(Object element, QName elementName) throws JAXBException {
if (element == null) {
return null;
}
if (element instanceof Element) {
return DOMUtil.serializeDOMToString((Element) element);
} else if (element instanceof JAXBElement) {
return marshalElementToString((JAXBElement<?>)element);
} else {
JAXBElement<Object> jaxbElement = new JAXBElement<Object>(elementName, Object.class, element);
return marshalElementToString(jaxbElement);
}
}
public void marshalToDom(Objectable objectable, Node parentNode) throws JAXBException {
QName elementQName = determineElementQName(objectable);
JAXBElement<Object> jaxbElement = new JAXBElement<Object>(elementQName, (Class) objectable.getClass(), objectable);
marshalElementToDom(jaxbElement, parentNode);
}
public void marshalElementToDom(JAXBElement<?> jaxbElement, Node parentNode) throws JAXBException {
getMarshaller().marshal(jaxbElement, parentNode);
}
public <T> Element marshalElementToDom(JAXBElement<T> jaxbElement, Document doc) throws JAXBException {
if (doc == null) {
doc = DOMUtil.getDocument();
}
Element element = doc.createElementNS(jaxbElement.getName().getNamespaceURI(), jaxbElement.getName().getLocalPart());
marshalElementToDom(jaxbElement, element);
return (Element) element.getFirstChild();
}
public <T> Element marshalObjectToDom(T jaxbObject, QName elementQName) throws JAXBException {
return marshalObjectToDom(jaxbObject, elementQName, (Document) null);
}
public String marshalContainerableToString(Containerable containerable) throws JAXBException {
return marshalObjectToString(containerable, determineElementQName(containerable));
}
public <T> String marshalObjectToString(T jaxbObject, QName elementQName) throws JAXBException {
JAXBElement<Object> jaxbElement = new JAXBElement<Object>(elementQName, (Class) jaxbObject.getClass(), jaxbObject);
return marshalElementToString(jaxbElement);
}
public <T> Element marshalObjectToDom(T jaxbObject, QName elementQName, Document doc) throws JAXBException {
if (doc == null) {
doc = DOMUtil.getDocument();
}
JAXBElement<T> jaxbElement = new JAXBElement<T>(elementQName, (Class<T>) jaxbObject.getClass(),
jaxbObject);
Element element = doc.createElementNS(elementQName.getNamespaceURI(), elementQName.getLocalPart());
marshalElementToDom(jaxbElement, element);
return (Element) element.getFirstChild();
}
public <T> void marshalObjectToDom(T jaxbObject, QName elementQName, Element parentElement) throws JAXBException {
JAXBElement<T> jaxbElement = new JAXBElement<T>(elementQName, (Class<T>) jaxbObject.getClass(),
jaxbObject);
marshalElementToDom(jaxbElement, parentElement);
}
public Element toDomElement(Object element) throws JAXBException {
return toDomElement(element, DOMUtil.getDocument());
}
public Element toDomElement(Object jaxbElement, Document doc) throws JAXBException {
return toDomElement(jaxbElement,doc,false,false,false);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public Element toDomElement(Object jaxbElement, Document doc, boolean adopt, boolean clone, boolean deep) throws JAXBException {
if (jaxbElement == null) {
return null;
}
if (jaxbElement instanceof Element) {
Element domElement = (Element) jaxbElement;
if (clone) {
domElement = (Element) domElement.cloneNode(deep);
}
if (domElement.getOwnerDocument().equals(doc)) {
return domElement;
}
if (adopt) {
doc.adoptNode(domElement);
}
return domElement;
} else if (jaxbElement instanceof JAXBElement) {
return marshalElementToDom((JAXBElement) jaxbElement, doc);
} else {
throw new IllegalArgumentException("Not an element: " + jaxbElement + " ("
+ jaxbElement.getClass().getName() + ")");
}
}
public <T> JAXBElement<T> unmarshalElement(String xmlString, Class<T> type) throws JAXBException, SchemaException {
if (xmlString == null) {
return null;
}
xmlString = xmlString.trim();
if (!xmlString.startsWith("<") || !xmlString.endsWith(">")) {
throw new IllegalArgumentException("Provided string is unlikely to be an XML");
}
StringReader reader = null;
try {
reader = new StringReader(xmlString);
JAXBElement<T> element = unmarshalElement(reader, type);
adopt(element);
return element;
} finally {
if (reader != null) {
IOUtils.closeQuietly(reader);
}
}
}
public <T> T unmarshalObject(InputStream input, Class<T> type) throws JAXBException, SchemaException {
Object object = getUnmarshaller().unmarshal(input);
JAXBElement<T> jaxbElement = (JAXBElement<T>) object;
adopt(jaxbElement);
if (jaxbElement == null) {
return null;
}
T value = jaxbElement.getValue();
// adopt not needed, already adopted in unmarshalElement call above
return value;
}
public <T> T unmarshalObject(InputStream input) throws JAXBException, SchemaException {
Object object = getUnmarshaller().unmarshal(input);
JAXBElement<T> jaxbElement = (JAXBElement<T>) object;
adopt(jaxbElement);
if (jaxbElement == null) {
return null;
}
T value = jaxbElement.getValue();
// adopt not needed, already adopted in unmarshalElement call above
return value;
}
public <T> JAXBElement<T> unmarshalElement(Reader reader, Class<T> type) throws JAXBException, SchemaException {
Object object = getUnmarshaller().unmarshal(reader);
JAXBElement<T> jaxbElement = (JAXBElement<T>) object;
adopt(jaxbElement);
return jaxbElement;
}
public <T> T unmarshalToObject(Node node, Class<T> type) throws JAXBException, SchemaException {
JAXBElement<T> element = unmarshalElement(node, type);
if (element == null) {
return null;
}
adopt(element);
return element.getValue();
}
public <T> JAXBElement<T> unmarshalElement(Node node, Class<T> type) throws JAXBException, SchemaException {
Object object = createUnmarshaller().unmarshal(node);
JAXBElement<T> jaxbElement = (JAXBElement<T>) object;
adopt(jaxbElement);
return jaxbElement;
}
public <T> T unmarshalObject(File file, Class<T> type) throws JAXBException, SchemaException, FileNotFoundException {
JAXBElement<T> element = unmarshalElement(file, type);
if (element == null) {
return null;
}
T value = element.getValue();
// adopt not needed, already adopted in unmarshalElement call above
if (!type.isAssignableFrom(value.getClass())) {
throw new IllegalArgumentException("Unmarshalled "+value.getClass()+" from file "+file+" while "+type+" was expected");
}
return value;
}
public Object unmarshalObjects(File file) throws JAXBException{
return createUnmarshaller().unmarshal(file);
}
public <T> T unmarshalObject(String stringXml, Class<T> type) throws JAXBException, SchemaException {
JAXBElement<T> element = unmarshalElement(stringXml, type);
if (element == null) {
return null;
}
T value = element.getValue();
adopt(value, type);
return value;
}
// element name must correspond to the name that points to the container definition
public <T extends Containerable> PrismContainer<T> unmarshalSingleValueContainer(File file, Class<T> type) throws JAXBException, SchemaException, FileNotFoundException {
return unmarshalSingleValueContainer(unmarshalElement(file, type));
}
// element name must correspond to the name that points to the container definition
public <T extends Containerable> PrismContainer<T> unmarshalSingleValueContainer(String stringXml, Class<T> type) throws JAXBException, SchemaException {
return unmarshalSingleValueContainer(unmarshalElement(stringXml, type));
}
// element name must correspond to the name that points to the container definition
private <T extends Containerable> PrismContainer<T> unmarshalSingleValueContainer(JAXBElement<T> element) throws JAXBException, SchemaException {
if (element == null) {
return null;
}
T value = element.getValue();
// this is a bit tricky - we have to create a container and put the newly obtained value into it
PrismContainerValue<T> containerValue = value.asPrismContainerValue();
containerValue.revive(prismContext);
PrismContainerDefinition<T> definition = prismContext.getSchemaRegistry().findContainerDefinitionByElementName(element.getName());
if (definition == null) {
throw new IllegalStateException("There's no container definition for element name " + element.getName());
}
containerValue.applyDefinition(definition, false);
PrismContainer container = definition.instantiate();
container.add(containerValue);
return container;
}
public <T> T unmarshalObject(Object domOrJaxbElement, Class<T> type) throws SchemaException {
JAXBElement<T> element;
if (domOrJaxbElement instanceof JAXBElement<?>) {
element = (JAXBElement<T>) domOrJaxbElement;
} else if (domOrJaxbElement instanceof Node) {
try {
element = unmarshalElement((Node)domOrJaxbElement, type);
} catch (JAXBException e) {
throw new SchemaException(e.getMessage(),e);
}
} else {
throw new IllegalArgumentException("Unknown element type "+domOrJaxbElement);
}
if (element == null) {
return null;
}
T value = element.getValue();
adopt(value, type);
return value;
}
public <T> JAXBElement<T> unmarshalElement(File file, Class<T> type) throws SchemaException, FileNotFoundException, JAXBException {
if (file == null) {
throw new IllegalArgumentException("File argument must not be null.");
}
InputStream is = null;
try {
is = new FileInputStream(file);
JAXBElement<T> element = (JAXBElement<T>) getUnmarshaller().unmarshal(is);
adopt(element);
return element;
} catch (RuntimeException ex){
throw new SystemException(ex);
} finally {
if (is != null) {
IOUtils.closeQuietly(is);
}
}
}
public <T> JAXBElement<T> unmarshalElement(InputStream is, Class<T> type) throws SchemaException, FileNotFoundException, JAXBException {
try {
JAXBElement<T> element = (JAXBElement<T>) getUnmarshaller().unmarshal(is);
adopt(element);
return element;
} finally {
if (is != null) {
IOUtils.closeQuietly(is);
}
}
}
public <T> T unmarshalRootObject(File file, Class<T> type) throws JAXBException, FileNotFoundException, SchemaException {
Validate.notNull(file, "File must not be null.");
InputStream is = null;
try {
is = new FileInputStream(file);
T object = (T) getUnmarshaller().unmarshal(is);
adopt(object);
return object;
} finally {
if (is != null) {
IOUtils.closeQuietly(is);
}
}
}
public boolean compareAny(List<Object> a, List<Object> b) {
if (a == b) {
return true;
}
if (a == null && b == null) {
return true;
}
if (a == null || b == null) {
return false;
}
if (a.size() != b.size()) {
return false;
}
for (int i = 0; i < a.size(); i++) {
if (!compareElement(a.get(i), b.get(i))) {
return false;
}
}
return true;
}
@SuppressWarnings("unchecked")
private boolean compareElement(Object a, Object b) {
if (a == b) {
return true;
}
if (a == null && b == null) {
return true;
}
if (a == null || b == null) {
return false;
}
Document doc = null;
Element ae = null;
Element be = null;
if (a instanceof Element) {
ae = (Element) a;
} else if (a instanceof JAXBElement) {
if (doc == null) {
doc = DOMUtil.getDocument();
}
try {
ae = marshalElementToDom((JAXBElement) a, doc);
} catch (JAXBException e) {
throw new IllegalStateException("Failed to marshall element " + a, e);
}
} else {
throw new IllegalArgumentException("Got unexpected type " + a.getClass().getName() + ": " + a);
}
if (b instanceof Element) {
be = (Element) b;
} else if (a instanceof JAXBElement) {
if (doc == null) {
doc = DOMUtil.getDocument();
}
try {
be = marshalElementToDom((JAXBElement) a, doc);
} catch (JAXBException e) {
throw new IllegalStateException("Failed to marshall element " + b, e);
}
} else {
throw new IllegalArgumentException("Got unexpected type " + b.getClass().getName() + ": " + b);
}
return DOMUtil.compareElement(ae, be, true);
}
public <T> T fromElement(Object element, Class<T> type) throws SchemaException {
if (element == null) {
return null;
}
if (type.isAssignableFrom(element.getClass())) {
return (T) element;
}
if (element instanceof JAXBElement) {
if (((JAXBElement) element).getValue() == null) {
return null;
}
if (type.isAssignableFrom(((JAXBElement) element).getValue().getClass())) {
return (T) ((JAXBElement) element).getValue();
}
}
if (element instanceof Element) {
try {
JAXBElement<T> unmarshalledElement = unmarshalElement((Element)element, type);
return unmarshalledElement.getValue();
} catch (JAXBException e) {
throw new IllegalArgumentException("Unmarshall failed: " + e.getMessage(),e);
}
}
throw new IllegalArgumentException("Unknown element type "+element.getClass().getName());
}
private QName determineElementQName(Objectable objectable) {
PrismObject<?> prismObject = objectable.asPrismObject();
if (prismObject.getElementName() != null) {
return prismObject.getElementName();
}
PrismObjectDefinition<?> definition = prismObject.getDefinition();
if (definition != null) {
if (definition.getName() != null) {
return definition.getName();
}
}
throw new IllegalStateException("Cannot determine element name of "+objectable);
}
private QName determineElementQName(Containerable containerable) {
PrismContainerValue prismContainerValue = containerable.asPrismContainerValue();
PrismContainerDefinition<?> definition = prismContainerValue.getParent() != null ? prismContainerValue.getParent().getDefinition() : null;
if (definition != null) {
if (definition.getName() != null) {
return definition.getName();
}
}
throw new IllegalStateException("Cannot determine element name of " + containerable + " (parent = " + prismContainerValue.getParent() + ", definition = " + definition + ")");
}
private boolean isObjectable(Class type) {
return Objectable.class.isAssignableFrom(type);
}
private <T> void adopt(T object, Class<T> type) throws SchemaException {
if (object instanceof Objectable) {
getPrismContext().adopt(((Objectable)object));
}
}
private void adopt(Object object) throws SchemaException {
if (object instanceof JAXBElement) {
adopt(((JAXBElement)object).getValue());
} else if (object instanceof Objectable) {
getPrismContext().adopt(((Objectable)(object)));
}
}
public static String marshalWrap(Object jaxbObject) throws JAXBException {
JAXBElement<Object> jaxbElement = new JAXBElement<Object>(DEFAULT_ELEMENT_NAME, (Class) jaxbObject.getClass(), jaxbObject);
return getInstance().marshalElementToString(jaxbElement);
}
}