/* * Copyright (c) 2012 Data Harmonisation Panel * * All rights reserved. This program and the accompanying materials are made * available under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution. If not, see <http://www.gnu.org/licenses/>. * * Contributors: * HUMBOLDT EU Integrated Project #030962 * Data Harmonisation Panel <http://www.dhpanel.eu> */ package eu.esdihumboldt.hale.io.gml.writer.internal; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.UUID; import javax.annotation.Nullable; import javax.xml.XMLConstants; import javax.xml.namespace.QName; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; import de.fhg.igd.slf4jplus.ALogger; import de.fhg.igd.slf4jplus.ALoggerFactory; import eu.esdihumboldt.hale.common.instance.model.Group; import eu.esdihumboldt.hale.common.schema.model.ChildDefinition; import eu.esdihumboldt.hale.common.schema.model.DefinitionGroup; import eu.esdihumboldt.hale.common.schema.model.DefinitionUtil; import eu.esdihumboldt.hale.common.schema.model.PropertyDefinition; import eu.esdihumboldt.hale.common.schema.model.TypeDefinition; import eu.esdihumboldt.hale.common.schema.model.constraint.property.Cardinality; import eu.esdihumboldt.hale.common.schema.model.constraint.property.NillableFlag; import eu.esdihumboldt.hale.io.gml.geometry.GMLConstants; import eu.esdihumboldt.hale.io.gml.internal.simpletype.SimpleTypeUtil; import eu.esdihumboldt.hale.io.gml.writer.internal.geometry.DefinitionPath; import eu.esdihumboldt.hale.io.gml.writer.internal.geometry.PathElement; import eu.esdihumboldt.hale.io.xsd.constraint.XmlAttributeFlag; import eu.esdihumboldt.hale.io.xsd.constraint.XmlElements; import eu.esdihumboldt.hale.io.xsd.model.XmlElement; /** * Utility methods used for the GML writer * * @author Simon Templer * @partner 01 / Fraunhofer Institute for Computer Graphics Research */ public abstract class GmlWriterUtil implements GMLConstants { private static final ALogger log = ALoggerFactory.getLogger(GmlWriterUtil.class); /** * Get the element name from a type definition * * @param type the type definition * @return the element name */ public static QName getElementName(TypeDefinition type) { Collection<? extends XmlElement> elements = type.getConstraint(XmlElements.class) .getElements(); if (elements == null || elements.isEmpty()) { log.debug("No schema element for type " + type.getDisplayName() + //$NON-NLS-1$ " found, using type name instead"); //$NON-NLS-1$ return type.getName(); } else { QName elementName = elements.iterator().next().getName(); if (elements.size() > 1) { log.warn("Multiple element definitions for type " + //$NON-NLS-1$ type.getDisplayName() + " found, using element " + //$NON-NLS-1$ elementName.getLocalPart()); } return elementName; } } /** * Add a namespace to the given XML stream writer * * @param writer the XML stream writer * @param namespace the namespace to add * @param preferredPrefix the preferred prefix * @throws XMLStreamException if setting a prefix for the namespace fails */ public static void addNamespace(XMLStreamWriter writer, String namespace, String preferredPrefix) throws XMLStreamException { if (writer.getPrefix(namespace) == null) { // no prefix for schema instance namespace String prefix = preferredPrefix; String ns = writer.getNamespaceContext().getNamespaceURI(prefix); if (ns == null) { // add xsi namespace writer.setPrefix(prefix, namespace); } else { int i = 0; while (ns != null) { ns = writer.getNamespaceContext().getNamespaceURI(prefix + "-" + (++i)); //$NON-NLS-1$ } writer.setPrefix(prefix + "-" + i, namespace); //$NON-NLS-1$ } } } /** * Determines if the given type represents a XML ID * * @param type the type definition * @return if the type represents an ID */ public static boolean isID(TypeDefinition type) { if (type.getName().equals(new QName(XMLConstants.W3C_XML_SCHEMA_NS_URI, "ID"))) { //$NON-NLS-1$ //$NON-NLS-2$ return true; } if (type.getSuperType() != null) { return isID(type.getSuperType()); } else { return false; } } /** * Determine if a given type is a feature type. * * @param type the type definition * @return if the type represents a feature type */ public static boolean isFeatureType(TypeDefinition type) { if ("AbstractFeatureType".equals(type.getName().getLocalPart()) && type.getName().getNamespaceURI().startsWith(GML_NAMESPACE_CORE)) { return true; } if (type.getSuperType() != null) { return isFeatureType(type.getSuperType()); } return false; } /** * Write a property attribute * * @param writer the XML stream writer * @param value the attribute value, may be <code>null</code> * @param propDef the attribute definition * @throws XMLStreamException if writing the attribute fails */ public static void writeAttribute(XMLStreamWriter writer, Object value, PropertyDefinition propDef) throws XMLStreamException { if (value == null) { long min = propDef.getConstraint(Cardinality.class).getMinOccurs(); if (min > 0) { if (!propDef.getConstraint(NillableFlag.class).isEnabled()) { log.warn("Non-nillable attribute " + propDef.getName() + " is null"); //$NON-NLS-1$ //$NON-NLS-2$ } else { // XXX write null attribute?! writeAtt(writer, null, propDef); } } } else { writeAtt(writer, SimpleTypeUtil.convertToXml(value, propDef.getPropertyType()), propDef); } } private static void writeAtt(XMLStreamWriter writer, String value, PropertyDefinition propDef) throws XMLStreamException { String ns = propDef.getName().getNamespaceURI(); if (ns != null && !ns.isEmpty()) { writer.writeAttribute(ns, propDef.getName().getLocalPart(), (value != null) ? (value) : (null)); } else { // no namespace writer.writeAttribute(propDef.getName().getLocalPart(), (value != null) ? (value) : (null)); } } /** * Write any required ID attribute, generating a random ID if needed. * * @param writer the XML stream writer * @param type the type definition * @param parent the parent object, may be <code>null</code>. If it is set * the value for the ID will be tried to be retrieved from the * parent object, otherwise a random ID will be generated * @param onlyIfNotSet if the ID shall only be written if no value is set in * the parent object * @throws XMLStreamException if an error occurs writing the ID */ public static void writeRequiredID(XMLStreamWriter writer, DefinitionGroup type, Group parent, boolean onlyIfNotSet) throws XMLStreamException { writeID(writer, type, parent, onlyIfNotSet, null); } /** * Write any required ID attribute, generating a random ID if needed. If a * desired ID is given, write it even if the attribute is not required. * * @param writer the XML stream writer * @param type the type definition * @param parent the parent object, may be <code>null</code>. If it is set * the value for the ID will be tried to be retrieved from the * parent object, otherwise a random ID will be generated * @param onlyIfNotSet if the ID shall only be written if no value is set in * the parent object * @param desiredId a desired identifier or <code>null</code> * @throws XMLStreamException if an error occurs writing the ID */ public static void writeID(XMLStreamWriter writer, DefinitionGroup type, Group parent, boolean onlyIfNotSet, @Nullable String desiredId) throws XMLStreamException { // find ID attribute PropertyDefinition idProp = null; for (PropertyDefinition prop : collectProperties(DefinitionUtil.getAllChildren(type))) { if (prop.getConstraint(XmlAttributeFlag.class).isEnabled() && (desiredId != null || prop.getConstraint(Cardinality.class).getMinOccurs() > 0) && isID(prop.getPropertyType())) { idProp = prop; break; // we assume there is only one ID attribute } } if (idProp == null) { // no ID attribute found return; } Object value = null; if (parent != null) { Object[] values = parent.getProperty(idProp.getName()); if (values != null && values.length > 0) { value = values[0]; } if (value != null && onlyIfNotSet) { // don't write the ID return; } } if (value == null) { value = desiredId; } if (value != null) { writeAttribute(writer, value, idProp); } else { UUID genID = UUID.randomUUID(); writeAttribute(writer, "_" + genID.toString(), idProp); //$NON-NLS-1$ } } /** * Write the opening element of a {@link PathElement} to the given stream * writer * * @param writer the stream writer * @param step the path element * @param generateRequiredID if required IDs shall be generated for the path * element * @throws XMLStreamException if writing to the stream writer fails */ public static void writeStartPathElement(XMLStreamWriter writer, PathElement step, boolean generateRequiredID) throws XMLStreamException { QName name = step.getName(); if (!step.isTransient()) { writeStartElement(writer, name); // write eventual required ID if (generateRequiredID) { GmlWriterUtil.writeRequiredID(writer, step.getType(), null, false); } // write additional attributes/elements according to the path // element step.prepareWrite(writer); } } /** * Collect all property definitions defined by the given child definitions, * i.e. returns a flattened version of the children. * * @param children the child definitions * @return the property definitions */ private static Collection<PropertyDefinition> collectProperties( Iterable<? extends ChildDefinition<?>> children) { List<PropertyDefinition> result = new ArrayList<PropertyDefinition>(); for (ChildDefinition<?> child : children) { if (child.asProperty() != null) { result.add(child.asProperty()); } else if (child.asGroup() != null) { result.addAll(collectProperties(child.asGroup().getDeclaredChildren())); } } return result; } /** * Collect all the paths to all child properties, even those contained in * groups. * * @param children the children * @param basePath the base path * @param elementsOnly if only properties representing an XML element should * be considered * @return the child paths, each ending with a property element */ public static Collection<DefinitionPath> collectPropertyPaths( Iterable<? extends ChildDefinition<?>> children, DefinitionPath basePath, boolean elementsOnly) { List<DefinitionPath> result = new ArrayList<DefinitionPath>(); for (ChildDefinition<?> child : children) { if (child.asProperty() != null) { if (!elementsOnly || !child.asProperty().getConstraint(XmlAttributeFlag.class).isEnabled()) { DefinitionPath path = new DefinitionPath(basePath); path.addProperty(child.asProperty()); result.add(path); } } else if (child.asGroup() != null) { DefinitionPath path = new DefinitionPath(basePath); path.addGroup(child.asGroup()); result.addAll(collectPropertyPaths(child.asGroup().getDeclaredChildren(), path, elementsOnly)); } } return result; } /** * Write a start element. * * @param writer the writer * @param name the element name * @throws XMLStreamException if an error occurs writing the start element */ public static void writeStartElement(XMLStreamWriter writer, QName name) throws XMLStreamException { String ns = name.getNamespaceURI(); if (ns != null && !ns.isEmpty()) { writer.writeStartElement(name.getNamespaceURI(), name.getLocalPart()); } else { writer.writeStartElement(name.getLocalPart()); } } /** * Write an empty element. * * @param writer the writer * @param name the element name * @throws XMLStreamException if an error occurs writing the empty element */ public static void writeEmptyElement(XMLStreamWriter writer, QName name) throws XMLStreamException { String ns = name.getNamespaceURI(); if (ns != null && !ns.isEmpty()) { writer.writeEmptyElement(name.getNamespaceURI(), name.getLocalPart()); } else { writer.writeEmptyElement(name.getLocalPart()); } } }