/* * Copyright (c) 2010-2013 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.xml; import java.io.File; import java.io.IOException; import java.math.BigDecimal; import java.math.BigInteger; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import javax.xml.XMLConstants; import javax.xml.datatype.Duration; import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.namespace.QName; import com.evolveum.prism.xml.ns._public.types_3.ItemPathType; import org.apache.commons.lang.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.w3c.dom.Element; import com.evolveum.midpoint.prism.PrismConstants; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.polystring.PolyString; import com.evolveum.midpoint.util.DOMUtil; import com.evolveum.midpoint.util.QNameUtil; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; /** * Maintains mapping of XSD types (qnames) and Java types (classes) * * @author Radovan Semancik */ public class XsdTypeMapper { public static final String BOOLEAN_XML_VALUE_TRUE = "true"; public static final String BOOLEAN_XML_VALUE_FALSE = "false"; private static final Map<Class, QName> javaToXsdTypeMap = new HashMap<>(); private static final Map<QName, Class> xsdToJavaTypeMap = new HashMap<>(); private static final Map<Class, QName> javaToXsdTypeMapExt = new HashMap<>(); private static final Map<QName, Class> xsdToJavaTypeMapExt = new HashMap<>(); private static final Trace LOGGER = TraceManager.getTrace(XsdTypeMapper.class); private static final String MULTIPLICITY_UNBOUNDED = "unbounded"; private static void initTypeMap() throws IOException, ClassNotFoundException { addMapping(String.class, DOMUtil.XSD_STRING, true); addMapping(char.class, DOMUtil.XSD_STRING, false); addMapping(File.class, DOMUtil.XSD_STRING, false); addMapping(int.class, DOMUtil.XSD_INT, true); addMapping(Integer.class, DOMUtil.XSD_INT, false); addMapping(BigInteger.class, DOMUtil.XSD_INTEGER, true); addMapping(BigDecimal.class, DOMUtil.XSD_DECIMAL, true); addMapping(double.class, DOMUtil.XSD_DOUBLE, true); addMapping(Double.class, DOMUtil.XSD_DOUBLE, false); addMapping(float.class, DOMUtil.XSD_FLOAT, true); addMapping(Float.class, DOMUtil.XSD_FLOAT, false); //maybe this is not a great idea addMapping(long.class, DOMUtil.XSD_LONG, true); addMapping(Long.class, DOMUtil.XSD_LONG, false); addMapping(short.class, DOMUtil.XSD_SHORT, true); addMapping(Short.class, DOMUtil.XSD_SHORT, false); addMapping(byte.class, DOMUtil.XSD_BYTE, true); addMapping(Byte.class, DOMUtil.XSD_BYTE, false); //great idea end addMapping(boolean.class, DOMUtil.XSD_BOOLEAN, true); addMapping(Boolean.class, DOMUtil.XSD_BOOLEAN, false); addMapping(byte[].class, DOMUtil.XSD_BASE64BINARY, true); addMapping(GregorianCalendar.class, DOMUtil.XSD_DATETIME, true); addMapping(XMLGregorianCalendar.class, DOMUtil.XSD_DATETIME, true); addMapping(Duration.class, DOMUtil.XSD_DURATION, true); addMapping(ItemPathType.class, ItemPathType.COMPLEX_TYPE, true); addMapping(ItemPath.class, ItemPathType.COMPLEX_TYPE, false); addMapping(QName.class, DOMUtil.XSD_QNAME, true); addMapping(PolyString.class, PrismConstants.POLYSTRING_TYPE_QNAME, true); addMappingExt(ItemPathType.class, ItemPathType.COMPLEX_TYPE, true); // TODO remove xsdToJavaTypeMap.put(DOMUtil.XSD_ANYURI, String.class); } private static void addMapping(Class javaClass, QName xsdType, boolean both) { LOGGER.trace("Adding XSD type mapping {} {} {} ", javaClass, both ? "<->" : " ->", xsdType); javaToXsdTypeMap.put(javaClass, xsdType); if (both) { xsdToJavaTypeMap.put(xsdType, javaClass); } } private static void addMappingExt(Class javaClass, QName xsdType, boolean both) { LOGGER.trace("Adding 'ext' XSD type mapping {} {} {} ", javaClass, both ? "<->" : " ->", xsdType); javaToXsdTypeMapExt.put(javaClass, xsdType); if (both) { xsdToJavaTypeMapExt.put(xsdType, javaClass); } } public static QName toXsdType(Class javaClass) { QName xsdType = getJavaToXsdMapping(javaClass); if (xsdType == null) { throw new IllegalArgumentException("No XSD mapping for Java type " + javaClass.getCanonicalName()); } return xsdType; } public static QName getJavaToXsdMapping(Class<?> type) { if (javaToXsdTypeMap.containsKey(type)) { return javaToXsdTypeMap.get(type); } Class<?> superType = type.getSuperclass(); if (superType != null) { return getJavaToXsdMapping(superType); } return null; } public static QName determineQNameWithNs(QName xsdType){ if (StringUtils.isNotBlank(xsdType.getNamespaceURI())){ return xsdType; } Set<QName> keys = xsdToJavaTypeMap.keySet(); for (Iterator<QName> iterator = keys.iterator(); iterator.hasNext();){ QName key = iterator.next(); if (QNameUtil.match(key, xsdType)){ return key; } } return null; } public static <T> Class<T> getXsdToJavaMapping(QName xsdType) { Class clazz = xsdToJavaTypeMap.get(xsdType); if (clazz == null){ Set<QName> keys = xsdToJavaTypeMap.keySet(); for (Iterator<QName> iterator = keys.iterator(); iterator.hasNext();){ QName key = iterator.next(); if (QNameUtil.match(key, xsdType)){ return xsdToJavaTypeMap.get(key); } } } return xsdToJavaTypeMap.get(xsdType); } /** * Returns the class in the type mapping. * The class supplied by the caller may be a subclass of what we have in the map. * This returns the class that in the mapping. */ public static Class<?> getTypeFromClass(Class<?> clazz) { if (javaToXsdTypeMap.containsKey(clazz)) { return clazz; } Class<?> superClazz = clazz.getSuperclass(); if (superClazz != null) { return getTypeFromClass(superClazz); } return null; } @Nullable public static <T> Class<T> toJavaType(@NotNull QName xsdType) { //noinspection ConstantConditions return toJavaType(xsdToJavaTypeMap, xsdType, true); } @Nullable public static <T> Class<T> toJavaTypeIfKnown(@NotNull QName xsdType) { return toJavaType(xsdToJavaTypeMap, xsdType, false); } // experimental feature - covers all the classes public static <T> Class<T> toJavaTypeIfKnownExt(@NotNull QName xsdType) { Class<T> cls = toJavaType(xsdToJavaTypeMap, xsdType, false); if (cls != null) { return cls; } else { return toJavaType(xsdToJavaTypeMapExt, xsdType, false); } } @Nullable private static <T> Class<T> toJavaType(Map<QName, Class> map, @NotNull QName xsdType, boolean errorIfNoMapping) { Class<?> javaType = map.get(xsdType); if (javaType == null && StringUtils.isEmpty(xsdType.getNamespaceURI())) { // TODO check uniqueness w.r.t. other types... for (Map.Entry<QName,Class> entry : xsdToJavaTypeMap.entrySet()) { if (QNameUtil.match(entry.getKey(), xsdType)) { javaType = entry.getValue(); break; } } } if (javaType == null) { if (errorIfNoMapping && xsdType.getNamespaceURI().equals(XMLConstants.W3C_XML_SCHEMA_NS_URI)) { throw new IllegalArgumentException("No type mapping for XSD type " + xsdType); } else { return null; } } @SuppressWarnings("unchecked") Class<T> typedClass = (Class<T>) javaType; return typedClass; } public static String multiplicityToString(Integer integer) { if (integer == null) { return null; } if (integer < 0) { return MULTIPLICITY_UNBOUNDED; } return integer.toString(); } public static Integer multiplicityToInteger(String string) { if (string == null || StringUtils.isEmpty(string)) { return null; } if (MULTIPLICITY_UNBOUNDED.equals(string)) { return -1; } return Integer.valueOf(string); } public static boolean isMatchingMultiplicity(int number, int min, int max) { if (min >= 0 && number < min) { return false; } if (max >= 0 && number > max) { return false; } return true; } static { try { initTypeMap(); } catch (Exception e) { LOGGER.error("Cannot initialize XSD type mapping: " + e.getMessage(), e); throw new IllegalStateException("Cannot initialize XSD type mapping: " + e.getMessage(), e); } } }