Java tutorial
/*---------------- FILE HEADER KALYPSO ------------------------------------------ * * This file is part of kalypso. * Copyright (C) 2004 by: * * Technical University Hamburg-Harburg (TUHH) * Institute of River and coastal engineering * Denickestrae 22 * 21073 Hamburg, Germany * http://www.tuhh.de/wb * * and * * Bjoernsen Consulting Engineers (BCE) * Maria Trost 3 * 56070 Koblenz, Germany * http://www.bjoernsen.de * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Contact: * * E-Mail: * belger@bjoernsen.de * schlienger@bjoernsen.de * v.doemming@tuhh.de * * ---------------------------------------------------------------------------*/ package org.kalypso.gml; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import javax.xml.XMLConstants; import javax.xml.namespace.QName; import org.apache.commons.lang3.StringUtils; import org.kalypso.commons.xml.NS; import org.kalypso.commons.xml.NSPrefixProvider; import org.kalypso.commons.xml.NSUtilities; import org.kalypso.contribs.eclipse.core.runtime.StatusUtilities; import org.kalypso.gmlschema.GMLSchemaUtilities; import org.kalypso.gmlschema.IGMLSchema; import org.kalypso.gmlschema.feature.IFeatureType; import org.kalypso.gmlschema.property.IPropertyType; import org.kalypso.gmlschema.property.IValuePropertyType; import org.kalypso.gmlschema.property.relation.IRelationType; import org.kalypso.gmlschema.types.IMarshallingTypeHandler; import org.kalypso.gmlschema.types.ISimpleMarshallingTypeHandler; import org.kalypsodeegree.KalypsoDeegreePlugin; import org.kalypsodeegree.model.feature.Feature; import org.kalypsodeegree.model.feature.GMLWorkspace; import org.kalypsodeegree.model.feature.IXLinkedFeature; import org.xml.sax.Attributes; import org.xml.sax.ContentHandler; import org.xml.sax.ErrorHandler; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import org.xml.sax.XMLReader; import org.xml.sax.ext.LexicalHandler; import org.xml.sax.helpers.AttributesImpl; /** * Writes an GMLWorkspace into a {@link XMLReader}. * * @author Andreas von Dmming */ public class GMLSAXFactory { private final NSPrefixProvider m_nsMapper = NSUtilities.getNSProvider(); /** Namespace -> prefix: Contains the namespaces, whose prefix was already registered in the content handler. */ private final Map<String, String> m_usedPrefixes = new HashMap<>(); private final XMLReader m_reader; private final String m_gmlVersion; private final QName m_xlinkAttributeName; private final QName m_schemaLocationAttributeName; public GMLSAXFactory(final XMLReader reader, final String gmlVersion) { m_reader = reader; m_gmlVersion = gmlVersion; // Initialise after handler is set m_xlinkAttributeName = getPrefixedQName(new QName(NS.XLINK, "href")); m_schemaLocationAttributeName = getPrefixedQName(new QName(NS.XSD, "schemaLocation")); //$NON-NLS-1$ } public void process(final GMLWorkspace workspace) throws SAXException { final Feature rootFeature = workspace.getRootFeature(); final IFeatureType rootFT = rootFeature.getFeatureType(); final QName rootQName = rootFT.getQName(); // REMARK: always force the root element to have the empty namespace prefix final String rootNamespace = rootQName.getNamespaceURI(); m_usedPrefixes.put(rootNamespace, XMLConstants.DEFAULT_NS_PREFIX); forcePrefixes(workspace, rootNamespace); // Add schemalocation string final AttributesImpl a = new AttributesImpl(); final String schemaLocationString = workspace.getSchemaLocationString(); if (schemaLocationString != null && schemaLocationString.length() > 0) { addAttribute(a, m_schemaLocationAttributeName, "CDATA", schemaLocationString); } processFeature(rootFeature, a); } private void forcePrefixes(final GMLWorkspace workspace, final String rootNamespace) throws SAXException { final Set<String> forcedTypes = new TreeSet<>(); final String[] knownNamespaces = forceFeatureTypePrefixes(workspace); final String[] additionalNamespaces = forceAdditionalSchemaPrefixes(workspace); forcedTypes.addAll(Arrays.asList(knownNamespaces)); forcedTypes.addAll(Arrays.asList(additionalNamespaces)); forcedTypes.remove(rootNamespace); // REMARK: remove namespaces of some known simple types: the namespace definition is not needed for them forcedTypes.remove(XMLConstants.W3C_XML_SCHEMA_NS_URI); /* Add them all */ final ContentHandler contentHandler = m_reader.getContentHandler(); for (final String namespace : forcedTypes) { final String preferedPrefix = getPreferedPrefix(namespace); contentHandler.startPrefixMapping(preferedPrefix, namespace); } } private void addAttribute(final AttributesImpl a, final QName name, final String type, final String value) throws SAXException { final String namespaceURI = name.getNamespaceURI(); final String localName = name.getLocalPart(); m_reader.getContentHandler().startPrefixMapping(name.getPrefix(), namespaceURI); final String qName = formatPrefixedQName(name); a.addAttribute(namespaceURI, localName, qName, type, value); } private static String formatPrefixedQName(final QName name) { final String prefix = name.getPrefix(); final String localName = name.getLocalPart(); if (StringUtils.isBlank(prefix)) return localName; return prefix + ":" + localName; //$NON-NLS-1$ } private String[] forceAdditionalSchemaPrefixes(final GMLWorkspace workspace) { final Collection<String> namespaces = new ArrayList<>(); // TODO: bug... this may cause too much namespaces to bee written into the gml-file // Either, we must only write what we really have, or // we should have another look at the import of substituting namespaces final IGMLSchema gmlSchema = workspace.getGMLSchema(); // we may have additional schema, but no features using them (now) // We save these namespaces as prefixes, so if we reload the gml // the additional schema will also be loaded final IGMLSchema[] additionalSchemas = gmlSchema.getAdditionalSchemas(); for (final IGMLSchema additionalSchema : additionalSchemas) namespaces.add(additionalSchema.getTargetNamespace()); return namespaces.toArray(new String[namespaces.size()]); } /** * Forces all known prefixes to be defined in the root element else we get to much prefix definitions later */ private String[] forceFeatureTypePrefixes(final GMLWorkspace workspace) { final Collection<String> namespaces = new HashSet<>(); final IGMLSchema gmlSchema = workspace.getGMLSchema(); final IFeatureType[] featureTypes = gmlSchema.getAllFeatureTypes(); for (final IFeatureType featureType : featureTypes) { final QName qName = featureType.getQName(); namespaces.add(qName.getNamespaceURI()); final IPropertyType[] properties = featureType.getProperties(); for (final IPropertyType propertyType : properties) { if (!propertyType.isVirtual()) namespaces.add(propertyType.getQName().getNamespaceURI()); if (propertyType instanceof IValuePropertyType) { if (!propertyType.isVirtual()) { final QName valueQName = ((IValuePropertyType) propertyType).getValueQName(); if (valueQName != null) namespaces.add(valueQName.getNamespaceURI()); } } else if (propertyType instanceof IRelationType) { if (((IRelationType) propertyType).isInlineAble()) { final IFeatureType targetType = ((IRelationType) propertyType).getTargetFeatureType(); namespaces.add(targetType.getQName().getNamespaceURI()); } } } } return namespaces.toArray(new String[namespaces.size()]); } private String getPreferedPrefix(final String namespace) { if (m_usedPrefixes.containsKey(namespace)) return m_usedPrefixes.get(namespace); else { // Create new prefix and register it final String prefix = m_nsMapper.getPreferredPrefix(namespace, null); // FIXME: before: startPrefixMapping here m_usedPrefixes.put(namespace, prefix); return prefix; } } private QName getPrefixedQName(final QName qName) { final String uri = qName.getNamespaceURI(); // Check if already registered; return the qname from the map, because qName is not prefixed final String prefix = getPreferedPrefix(uri); return new QName(qName.getNamespaceURI(), qName.getLocalPart(), prefix); } private void processFeature(final Feature feature, final AttributesImpl attributes) throws SAXException { final ContentHandler contentHandler = m_reader.getContentHandler(); final IFeatureType featureType = feature.getFeatureType(); /* Add gml:id or fid attribute */ final String id = feature.getId(); if (id != null && id.length() > 0) { final String version = featureType.getGMLSchema().getGMLVersion(); final QName idQName = getPrefixedQName(GMLSchemaUtilities.getIdAttribute(version)); addAttribute(attributes, idQName, "CDATA", id); } /* Write opening tag */ final QName prefixedQName = getPrefixedQName(feature.getFeatureType().getQName()); final String localPart = prefixedQName.getLocalPart(); final String uri = prefixedQName.getNamespaceURI(); final String qname = elementQName(prefixedQName); contentHandler.startElement(uri, localPart, qname, attributes); /* Write properties */ final IPropertyType[] properties = featureType.getProperties(); for (final IPropertyType pt : properties) { // Virtual properties are properties which do not get serialized... if (pt.isVirtual()) continue; // REMARK: this only works for sequences of the property! // If the content of (i.e. the element itself has the maxOccurs > 1 this will not // work. In order to support this, we need a FeatureProperty object as value object (as in deegree2) final Object value = feature.getProperty(pt); if (pt.isList()) { final List<?> values = (List<?>) value; if (values != null) { /* FIXME: ConcurrentModificationExceptions happens sometimes here */ for (final Object propertyValue : values) processProperty(pt, propertyValue); } } else { // If value == null && minOccurs == 0 do not write an element if (value != null || pt.getMinOccurs() > 0) processProperty(pt, value); } } /* Write closing tag */ contentHandler.endElement(uri, localPart, qname); } private String elementQName(final QName prefixedQName) { final String prefix = prefixedQName.getPrefix(); if (prefix.isEmpty()) return prefixedQName.getLocalPart(); return prefix + ":" + prefixedQName.getLocalPart(); } /** * Writes one single property */ private void processProperty(final IPropertyType pt, final Object propertyValue) throws SAXException { final ContentHandler contentHandler = m_reader.getContentHandler(); final QName name = pt.getQName(); final QName prefixedQName = getPrefixedQName(name); final String uri = prefixedQName.getNamespaceURI(); final String localPart = prefixedQName.getLocalPart(); // Find attributes for the current property final Attributes atts = attributeForProperty(pt, propertyValue); /* Write starting tag */ final String qname = elementQName(prefixedQName); contentHandler.startElement(uri, localPart, qname, atts); if (pt instanceof IRelationType) { // Write the feature as content. If it is a reference (i.e. no feature), nothing is written, as the href was // already set as an attribute if (propertyValue instanceof Feature && !(propertyValue instanceof IXLinkedFeature)) processFeature((Feature) propertyValue, new AttributesImpl()); } else if (pt instanceof IValuePropertyType) processValueType((IValuePropertyType) pt, propertyValue, prefixedQName); else throw new UnsupportedOperationException(); /* Write ending tag */ contentHandler.endElement(uri, localPart, qname); } private Attributes attributeForProperty(final IPropertyType pt, final Object propertyValue) throws SAXException { final AttributesImpl atts = new AttributesImpl(); if (pt instanceof IRelationType) { final String href; if (propertyValue instanceof String) href = "#" + (String) propertyValue; else if (propertyValue instanceof IXLinkedFeature) href = ((IXLinkedFeature) propertyValue).getHref(); else href = null; if (href != null) addAttribute(atts, m_xlinkAttributeName, "CDATA", href); } return atts; } private String printSimpleValue(final IValuePropertyType pt, final ISimpleMarshallingTypeHandler<Object> th, final Object propertyValue) throws SAXException { if (pt.isFixed()) return pt.getFixed(); if (propertyValue == null) return null; try { return th.convertToXMLString(propertyValue); } catch (final Exception e) { final String msg = String.format("Could not convert value '%s' for property '%s'", propertyValue, pt.getQName()); throw new SAXException(msg, e); } } private void processValueType(final IValuePropertyType pt, final Object propertyValue, final QName prefixedQName) throws SAXException { final IMarshallingTypeHandler th = pt.getTypeHandler(); if (th instanceof ISimpleMarshallingTypeHandler<?>) { final String xmlString = printSimpleValue(pt, (ISimpleMarshallingTypeHandler<Object>) th, propertyValue); if (xmlString != null) { // FIXME: this is the right place to write CDATA stuff, but of course now it is a wild hack // to look for a specific value. This must of course be decided in a more general way. // Maybe we register extensions for specific qnames? // TODO: also, it should be only done for String, i.e. in the XsdBaseTypeHandlerString final boolean doCData = prefixedQName.equals(new QName(NS.OM, "result")); final LexicalHandler lexicalHandler = doCData ? (LexicalHandler) m_reader.getProperty("http://xml.org/sax/properties/lexical-handler") : null; if (doCData) lexicalHandler.startCDATA(); m_reader.getContentHandler().characters(xmlString.toCharArray(), 0, xmlString.length()); if (doCData) lexicalHandler.endCDATA(); } return; } if (propertyValue != null) { try { th.marshal(propertyValue, m_reader, null, m_gmlVersion); } catch (final Exception e) { // Catch any exception here: we should always continue to write data in order to minimise data loss here // TODO: we need an error handler! Else the user does not get any information about errors // TODO Distinguish between normal exceptions and SaxParseException final ErrorHandler errorHandler = m_reader.getErrorHandler(); if (errorHandler == null) KalypsoDeegreePlugin.getDefault().getLog().log(StatusUtilities.statusFromThrowable(e)); else errorHandler .error(new SAXParseException("Failed to write property: " + pt.getQName(), null, e)); } } } }