0001: //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/trunk/src/org/deegree/model/feature/GMLFeatureAdapter.java $
0002: /*---------------- FILE HEADER ------------------------------------------
0003:
0004: This file is part of deegree.
0005: Copyright (C) 2001-2008 by:
0006: EXSE, Department of Geography, University of Bonn
0007: http://www.giub.uni-bonn.de/deegree/
0008: lat/lon GmbH
0009: http://www.lat-lon.de
0010:
0011: This library is free software; you can redistribute it and/or
0012: modify it under the terms of the GNU Lesser General Public
0013: License as published by the Free Software Foundation; either
0014: version 2.1 of the License, or (at your option) any later version.
0015:
0016: This library is distributed in the hope that it will be useful,
0017: but WITHOUT ANY WARRANTY; without even the implied warranty of
0018: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0019: Lesser General Public License for more details.
0020:
0021: You should have received a copy of the GNU Lesser General Public
0022: License along with this library; if not, write to the Free Software
0023: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0024:
0025: Contact:
0026:
0027: Andreas Poth
0028: lat/lon GmbH
0029: Aennchenstraße 19
0030: 53177 Bonn
0031: Germany
0032: E-Mail: poth@lat-lon.de
0033:
0034: Prof. Dr. Klaus Greve
0035: Department of Geography
0036: University of Bonn
0037: Meckenheimer Allee 166
0038: 53115 Bonn
0039: Germany
0040: E-Mail: greve@giub.uni-bonn.de
0041:
0042: ---------------------------------------------------------------------------*/
0043: package org.deegree.model.feature;
0044:
0045: import java.io.ByteArrayInputStream;
0046: import java.io.ByteArrayOutputStream;
0047: import java.io.IOException;
0048: import java.io.OutputStream;
0049: import java.io.OutputStreamWriter;
0050: import java.io.PrintWriter;
0051: import java.math.BigDecimal;
0052: import java.net.URI;
0053: import java.sql.Timestamp;
0054: import java.util.Calendar;
0055: import java.util.Date;
0056: import java.util.HashMap;
0057: import java.util.HashSet;
0058: import java.util.Iterator;
0059: import java.util.Map;
0060: import java.util.Set;
0061:
0062: import org.deegree.datatypes.QualifiedName;
0063: import org.deegree.datatypes.Types;
0064: import org.deegree.framework.log.ILogger;
0065: import org.deegree.framework.log.LoggerFactory;
0066: import org.deegree.framework.util.CharsetUtils;
0067: import org.deegree.framework.util.StringTools;
0068: import org.deegree.framework.util.TimeTools;
0069: import org.deegree.framework.xml.DOMPrinter;
0070: import org.deegree.framework.xml.XMLException;
0071: import org.deegree.framework.xml.XMLFragment;
0072: import org.deegree.framework.xml.XMLTools;
0073: import org.deegree.io.datastore.schema.MappedFeaturePropertyType;
0074: import org.deegree.io.datastore.schema.MappedFeatureType;
0075: import org.deegree.model.feature.schema.FeatureType;
0076: import org.deegree.model.feature.schema.PropertyType;
0077: import org.deegree.model.spatialschema.Envelope;
0078: import org.deegree.model.spatialschema.GMLGeometryAdapter;
0079: import org.deegree.model.spatialschema.Geometry;
0080: import org.deegree.model.spatialschema.GeometryException;
0081: import org.deegree.ogcbase.CommonNamespaces;
0082: import org.w3c.dom.Element;
0083: import org.xml.sax.SAXException;
0084:
0085: /**
0086: * Exports feature instances to their GML representation.
0087: * <p>
0088: * Has support for XLink output and to disable XLink output (which is generally not feasible).
0089: * <p>
0090: * Also responsible for "xlinking" features: if a feature occurs several times in a feature collection, it must be
0091: * exported only once - all other occurences must use xlink-attributes in the surrounding property element to reference
0092: * the feature.
0093: *
0094: * TODO Handle FeatureCollections like ordinary Features (needs changes in feature model).
0095: *
0096: * TODO Separate cycle check (for suppressXLinkOutput).
0097: *
0098: * TODO Use a more straight-forward approach to export DOM representations.
0099: *
0100: * TODO Handle multiple application schemas (in xsi:schemaLocation attribute).
0101: *
0102: * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a>
0103: * @author last edited by: $Author: rbezema $
0104: *
0105: * @version $Revision: 10123 $, $Date: 2008-02-18 09:44:53 -0800 (Mon, 18 Feb 2008) $
0106: */
0107: public class GMLFeatureAdapter {
0108:
0109: private static final ILogger LOG = LoggerFactory
0110: .getLogger(GMLFeatureAdapter.class);
0111:
0112: private static final String WFS_SCHEMA_BINDING = "http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd";
0113:
0114: // values: feature ids of already exported features (for XLinks)
0115: private Set<String> exportedFeatures = new HashSet<String>();
0116:
0117: // values: feature ids of all (sub-) features in a feature (to find cyclic features)
0118: private Set<String> localFeatures = new HashSet<String>();
0119:
0120: private boolean suppressXLinkOutput;
0121:
0122: private String schemaURL;
0123:
0124: // marks if namespace bindings have been appended already
0125: private boolean nsBindingsExported;
0126:
0127: /**
0128: * Creates a new <code>GMLFeatureAdapter</code> instance with enabled XLink output.
0129: */
0130: public GMLFeatureAdapter() {
0131: this .suppressXLinkOutput = false;
0132: }
0133:
0134: /**
0135: * Creates a new <code>GMLFeatureAdapter</code> instance with enabled XLink output and schema reference.
0136: *
0137: * @param schemaURL
0138: * URL of schema document (used as xsi:schemaLocation attribute in XML output)
0139: */
0140: public GMLFeatureAdapter(String schemaURL) {
0141: this .suppressXLinkOutput = false;
0142: if (schemaURL != null) {
0143: this .schemaURL = StringTools.replace(schemaURL, "&",
0144: "&", true);
0145: }
0146: }
0147:
0148: /**
0149: * Creates a new instance <code>GMLFeatureAdapter</code> with configurable XLink output.
0150: *
0151: * @param suppressXLinkOutput
0152: * set to true, if no XLinks shall be used
0153: */
0154: public GMLFeatureAdapter(boolean suppressXLinkOutput) {
0155: this .suppressXLinkOutput = suppressXLinkOutput;
0156: }
0157:
0158: /**
0159: * Creates a new instance <code>GMLFeatureAdapter</code> with configurable XLink output.
0160: *
0161: * @param suppressXLinkOutput
0162: * set to true, if no XLinks shall be used
0163: * @param schemaURL
0164: * URL of schema document (used as xsi:schemaLocation attribute in XML output)
0165: */
0166: public GMLFeatureAdapter(boolean suppressXLinkOutput,
0167: String schemaURL) {
0168: this .suppressXLinkOutput = suppressXLinkOutput;
0169: if (schemaURL != null) {
0170: this .schemaURL = StringTools.replace(schemaURL, "&",
0171: "&", true);
0172: }
0173: }
0174:
0175: /**
0176: * Appends the DOM representation of the given feature to the also given <code>Node</code>.
0177: * <p>
0178: * TODO do this a better way (append nodes directly without serializing to string and parsing it again)
0179: *
0180: * @param root
0181: * @param feature
0182: * @throws FeatureException
0183: * @throws IOException
0184: * @throws SAXException
0185: */
0186: public void append(Element root, Feature feature)
0187: throws FeatureException, IOException, SAXException {
0188:
0189: GMLFeatureDocument doc = export(feature);
0190: XMLTools.insertNodeInto(doc.getRootElement(), root);
0191: }
0192:
0193: /**
0194: * Export a <code>Feature</code> to it's XML representation.
0195: *
0196: * @param feature
0197: * feature to export
0198: * @return XML representation of feature
0199: * @throws IOException
0200: * @throws FeatureException
0201: * @throws XMLException
0202: * @throws SAXException
0203: */
0204: public GMLFeatureDocument export(Feature feature)
0205: throws IOException, FeatureException, XMLException,
0206: SAXException {
0207:
0208: ByteArrayOutputStream bos = new ByteArrayOutputStream(20000);
0209: export(feature, bos);
0210: ByteArrayInputStream bis = new ByteArrayInputStream(bos
0211: .toByteArray());
0212: bos.close();
0213:
0214: GMLFeatureDocument doc = new GMLFeatureDocument();
0215: doc.load(bis, XMLFragment.DEFAULT_URL);
0216: return doc;
0217: }
0218:
0219: /**
0220: * Appends the DOM representation of the given <code>FeatureCollection</code> to the also given <code>Node</code>.
0221: * <p>
0222: * TODO do this a better way (append nodes directly without serializing to string and parsing it again)
0223: *
0224: * @param root
0225: * @param fc
0226: * @throws FeatureException
0227: * @throws IOException
0228: * @throws SAXException
0229: */
0230: public void append(Element root, FeatureCollection fc)
0231: throws FeatureException, IOException, SAXException {
0232:
0233: GMLFeatureCollectionDocument doc = export(fc);
0234: XMLTools.insertNodeInto(doc.getRootElement(), root);
0235: }
0236:
0237: /**
0238: * Export a <code>FeatureCollection</code> to it's XML representation.
0239: *
0240: * @param fc
0241: * feature collection
0242: * @return XML representation of feature collection
0243: * @throws IOException
0244: * @throws FeatureException
0245: * @throws XMLException
0246: * @throws SAXException
0247: */
0248: public GMLFeatureCollectionDocument export(FeatureCollection fc)
0249: throws IOException, FeatureException, XMLException,
0250: SAXException {
0251:
0252: ByteArrayOutputStream bos = new ByteArrayOutputStream(20000);
0253: export(fc, bos);
0254: ByteArrayInputStream bis = new ByteArrayInputStream(bos
0255: .toByteArray());
0256: bos.close();
0257:
0258: GMLFeatureCollectionDocument doc = new GMLFeatureCollectionDocument();
0259: doc.load(bis, XMLFragment.DEFAULT_URL);
0260: return doc;
0261: }
0262:
0263: /**
0264: * Exports an instance of a <code>FeatureCollection</code> to the passed <code>OutputStream</code> formatted as
0265: * GML. Uses the deegree system character set for the XML header encoding information.
0266: *
0267: * @param fc
0268: * feature collection to export
0269: * @param os
0270: * output stream to write to
0271: *
0272: * @throws IOException
0273: * @throws FeatureException
0274: */
0275: public void export(FeatureCollection fc, OutputStream os)
0276: throws IOException, FeatureException {
0277: export(fc, os, CharsetUtils.getSystemCharset());
0278: }
0279:
0280: /**
0281: * Exports a <code>FeatureCollection</code> instance to the passed <code>OutputStream</code> formatted as GML.
0282: *
0283: * @param fc
0284: * feature collection to export
0285: * @param os
0286: * output stream to write to
0287: * @param charsetName
0288: * name of the used charset/encoding (for the XML header)
0289: *
0290: * @throws IOException
0291: * @throws FeatureException
0292: */
0293: public void export(FeatureCollection fc, OutputStream os,
0294: String charsetName) throws IOException, FeatureException {
0295:
0296: PrintWriter pw = new PrintWriter(new OutputStreamWriter(os,
0297: charsetName));
0298: pw.println("<?xml version=\"1.0\" encoding=\"" + charsetName
0299: + "\"?>");
0300: if (fc instanceof FeatureTupleCollection) {
0301: exportTupleCollection((FeatureTupleCollection) fc, pw);
0302: } else {
0303: exportRootCollection(fc, pw);
0304: }
0305: pw.close();
0306: }
0307:
0308: /**
0309: * Exports a <code>FeatureCollection</code> instance to the passed <code>OutputStream</code> formatted as GML.
0310: *
0311: * @param fc
0312: * feature collection to print/export
0313: * @param pw
0314: * target of the printing/export
0315: * @throws FeatureException
0316: */
0317: private void exportRootCollection(FeatureCollection fc,
0318: PrintWriter pw) throws FeatureException {
0319:
0320: Set<Feature> additionalRootLevelFeatures = determineAdditionalRootLevelFeatures(fc);
0321: if (this .suppressXLinkOutput
0322: && additionalRootLevelFeatures.size() > 0) {
0323: String msg = Messages.getString("ERROR_REFERENCE_TYPE");
0324: throw new FeatureException(msg);
0325: }
0326:
0327: if (fc.getId() != null && !"".equals(fc.getId())) {
0328: this .exportedFeatures.add(fc.getId());
0329: }
0330:
0331: // open the feature collection element
0332: pw.print("<");
0333: pw.print(fc.getName().getPrefixedName());
0334:
0335: // hack to correct the "numberOfFeatures" attribute (can not be set in any case, because
0336: // sometimes (resultType="hits") the collection contains no features at all -- but only the
0337: // attribute "numberOfFeatures"
0338: if (fc.size() > 0) {
0339: int hackedFeatureCount = fc.size()
0340: + additionalRootLevelFeatures.size();
0341: fc
0342: .setAttribute("numberOfFeatures", ""
0343: + hackedFeatureCount);
0344: }
0345:
0346: Map<String, String> attributes = fc.getAttributes();
0347: for (Iterator<String> iterator = attributes.keySet().iterator(); iterator
0348: .hasNext();) {
0349: String name = iterator.next();
0350: String value = attributes.get(name);
0351: pw.print(' ');
0352: pw.print(name);
0353: pw.print("='");
0354: pw.print(value);
0355: pw.print("'");
0356: }
0357:
0358: // determine and add namespace bindings
0359: Map<String, URI> nsBindings = determineUsedNSBindings(fc);
0360: if (LOG.getLevel() == ILogger.LOG_DEBUG) {
0361: LOG.logDebug(nsBindings.toString());
0362: }
0363: nsBindings.put("gml", CommonNamespaces.GMLNS);
0364: nsBindings.put("xlink", CommonNamespaces.XLNNS);
0365: if (this .schemaURL != null) {
0366: nsBindings.put("xsi", CommonNamespaces.XSINS);
0367: }
0368: appendNSBindings(nsBindings, pw);
0369:
0370: // add schema reference (if available)
0371: if (this .schemaURL != null && fc.size() > 0) {
0372: pw.print(" xsi:schemaLocation=\""
0373: + fc.getFeature(0).getName().getNamespace() + " ");
0374: pw.print(this .schemaURL + " ");
0375: pw.print(WFS_SCHEMA_BINDING + "\"");
0376: }
0377: pw.print('>');
0378:
0379: Envelope env = null;
0380: try {
0381: env = fc.getBoundedBy();
0382: } catch (GeometryException e) {
0383: // omit gml:boundedBy-element if featureCollection contains features
0384: // with different SRS (and their envelopes cannot be merged)
0385: }
0386: if (env != null) {
0387: pw.print("<gml:boundedBy><gml:Envelope");
0388: if (env.getCoordinateSystem() != null) {
0389: pw.print(" srsName='"
0390: + env.getCoordinateSystem().getPrefixedName()
0391: + "'");
0392: }
0393: pw.print("><gml:pos srsDimension='2'>");
0394: pw.print(env.getMin().getX());
0395: pw.print(' ');
0396: pw.print(env.getMin().getY());
0397: pw.print("</gml:pos><gml:pos srsDimension='2'>");
0398: pw.print(env.getMax().getX());
0399: pw.print(' ');
0400: pw.print(env.getMax().getY());
0401: pw.print("</gml:pos></gml:Envelope></gml:boundedBy>");
0402: }
0403:
0404: // export all contained features
0405: for (int i = 0; i < fc.size(); i++) {
0406: Feature feature = fc.getFeature(i);
0407: String fid = feature.getId();
0408: if (fid != null && !fid.equals("")
0409: && this .exportedFeatures.contains(fid)
0410: && !this .suppressXLinkOutput) {
0411: pw.print("<gml:featureMember xlink:href=\"#");
0412: pw.print(fid);
0413: pw.print("\"/>");
0414: } else {
0415: pw.print("<gml:featureMember>");
0416: export(feature, pw);
0417: pw.print("</gml:featureMember>");
0418: }
0419: }
0420:
0421: // export all additional root level features
0422: for (Feature feature : additionalRootLevelFeatures) {
0423: String fid = feature.getId();
0424: if (fid != null && !fid.equals("")
0425: && this .exportedFeatures.contains(fid)) {
0426: pw.print("<gml:featureMember xlink:href=\"#");
0427: pw.print(fid);
0428: pw.print("\"/>");
0429: } else {
0430: pw.print("<gml:featureMember>");
0431: export(feature, pw);
0432: pw.print("</gml:featureMember>");
0433: }
0434: }
0435:
0436: // close the feature collection element
0437: pw.print("</");
0438: pw.print(fc.getName().getPrefixedName());
0439: pw.print('>');
0440: }
0441:
0442: /**
0443: * Determines features that don't originally belong to the root level of the given <code>FeatureCollection</code>,
0444: * but which need to be put there in the output, because they are subfeatures inside of properties that have the
0445: * content type "gml:ReferenceType".
0446: *
0447: * @param fc
0448: * @return features to be added to the root level
0449: */
0450: private Set<Feature> determineAdditionalRootLevelFeatures(
0451: FeatureCollection fc) {
0452:
0453: Set<Feature> rootFeatures = new HashSet<Feature>(fc.size());
0454: for (int i = 0; i < fc.size(); i++) {
0455: rootFeatures.add(fc.getFeature(i));
0456: }
0457: Set<Feature> additionalRootFeatures = new HashSet<Feature>();
0458: Set<Feature> checkedFeatures = new HashSet<Feature>();
0459: for (int i = 0; i < fc.size(); i++) {
0460: Feature feature = fc.getFeature(i);
0461: determineAdditionalRootLevelFeatures(feature,
0462: additionalRootFeatures, rootFeatures,
0463: checkedFeatures);
0464: }
0465: return additionalRootFeatures;
0466: }
0467:
0468: /**
0469: * Determines features that don't originally belong to the root level of the given <code>FeatureCollection</code>,
0470: * but which need to be put there in the output, because they are subfeatures inside of properties that have the
0471: * content type "gml:ReferenceType".
0472: *
0473: * @param fc
0474: * @param additionalFeatures
0475: * to be added to the root level
0476: * @param rootFeatures
0477: * to be added to the root level
0478: * @param checkedFeatures
0479: * features that have already been checked
0480: */
0481: private void determineAdditionalRootLevelFeatures(Feature feature,
0482: Set<Feature> additionalFeatures, Set<Feature> rootFeatures,
0483: Set<Feature> checkedFeatures) {
0484: for (FeatureProperty property : feature.getProperties()) {
0485: Object value = property.getValue();
0486: if (value instanceof Feature) {
0487: Feature subFeature = (Feature) value;
0488: if (!checkedFeatures.contains(subFeature)) {
0489: if (feature.getFeatureType() != null
0490: && feature.getFeatureType() instanceof MappedFeatureType) {
0491: MappedFeatureType ft = (MappedFeatureType) feature
0492: .getFeatureType();
0493: MappedFeaturePropertyType pt = (MappedFeaturePropertyType) ft
0494: .getProperty(property.getName());
0495: assert pt != null;
0496: if (pt.isReferenceType()
0497: && !rootFeatures.contains(subFeature)) {
0498: additionalFeatures.add((Feature) value);
0499: }
0500: }
0501: checkedFeatures.add(subFeature);
0502: determineAdditionalRootLevelFeatures(subFeature,
0503: additionalFeatures, rootFeatures,
0504: checkedFeatures);
0505: }
0506: }
0507: }
0508: }
0509:
0510: /**
0511: * Exports a {@link FeatureTupleCollection} instance to the passed {@link OutputStream} formatted as GML.
0512: *
0513: * @param fc
0514: * feature tuple collection to print/export
0515: * @param pw
0516: * target of the printing/export
0517: * @throws FeatureException
0518: */
0519: private void exportTupleCollection(FeatureTupleCollection fc,
0520: PrintWriter pw) throws FeatureException {
0521:
0522: if (fc.getId() != null && !"".equals(fc.getId())) {
0523: this .exportedFeatures.add(fc.getId());
0524: }
0525:
0526: // open the feature collection element
0527: pw.print("<");
0528: pw.print(fc.getName().getPrefixedName());
0529:
0530: Map<String, String> attributes = fc.getAttributes();
0531: for (Iterator<String> iterator = attributes.keySet().iterator(); iterator
0532: .hasNext();) {
0533: String name = iterator.next();
0534: String value = attributes.get(name);
0535: pw.print(' ');
0536: pw.print(name);
0537: pw.print("='");
0538: pw.print(value);
0539: pw.print("'");
0540: }
0541:
0542: // determine and add namespace bindings
0543: Map<String, URI> nsBindings = determineUsedNSBindings(fc);
0544: nsBindings.put("gml", CommonNamespaces.GMLNS);
0545: nsBindings.put("xlink", CommonNamespaces.XLNNS);
0546: if (this .schemaURL != null) {
0547: nsBindings.put("xsi", CommonNamespaces.XSINS);
0548: }
0549: appendNSBindings(nsBindings, pw);
0550:
0551: // add schema reference (if available)
0552: if (this .schemaURL != null && fc.size() > 0) {
0553: pw.print(" xsi:schemaLocation=\""
0554: + fc.getTuple(0)[0].getName().getNamespace() + " ");
0555: pw.print(this .schemaURL + " ");
0556: pw.print(WFS_SCHEMA_BINDING + "\"");
0557: }
0558: pw.print('>');
0559:
0560: Envelope env = null;
0561: try {
0562: env = fc.getBoundedBy();
0563: } catch (GeometryException e) {
0564: // omit gml:boundedBy-element if featureCollection contains features
0565: // with different SRS (and their envelopes cannot be merged)
0566: }
0567: if (env != null) {
0568: pw.print("<gml:boundedBy><gml:Envelope");
0569: if (env.getCoordinateSystem() != null) {
0570: pw.print(" srsName='"
0571: + env.getCoordinateSystem().getPrefixedName()
0572: + "'");
0573: }
0574: pw.print("><gml:pos srsDimension='2'>");
0575: pw.print(env.getMin().getX());
0576: pw.print(' ');
0577: pw.print(env.getMin().getY());
0578: pw.print("</gml:pos><gml:pos srsDimension='2'>");
0579: pw.print(env.getMax().getX());
0580: pw.print(' ');
0581: pw.print(env.getMax().getY());
0582: pw.print("</gml:pos></gml:Envelope></gml:boundedBy>");
0583: }
0584:
0585: // export all contained feature tuples
0586: for (int i = 0; i < fc.numTuples(); i++) {
0587: Feature[] features = fc.getTuple(i);
0588: pw.print("<gml:featureTuple>");
0589: for (Feature feature : features) {
0590: export(feature, pw);
0591: }
0592: pw.print("</gml:featureTuple>");
0593: }
0594:
0595: // close the feature collection element
0596: pw.print("</");
0597: pw.print(fc.getName().getPrefixedName());
0598: pw.print('>');
0599: }
0600:
0601: /**
0602: * Determines the namespace bindings that are used in the feature collection.
0603: * <p>
0604: * NOTE: Currently only the bindings for the feature collection's root element and the contained features are
0605: * considered. If a subfeature uses another bindings, this binding will be missing in the XML.
0606: *
0607: * @param fc
0608: * feature collection
0609: * @return the namespace bindings.
0610: */
0611: private Map<String, URI> determineUsedNSBindings(
0612: FeatureCollection fc) {
0613:
0614: Map<String, URI> nsBindings = new HashMap<String, URI>();
0615:
0616: // process feature collection element
0617: QualifiedName name = fc.getName();
0618: nsBindings.put(name.getPrefix(), name.getNamespace());
0619:
0620: if (fc instanceof FeatureTupleCollection) {
0621: // process contained feature tuples
0622: FeatureTupleCollection ftc = (FeatureTupleCollection) fc;
0623: for (int i = 0; i < ftc.numTuples(); i++) {
0624: Feature[] features = ftc.getTuple(i);
0625: for (Feature feature : features) {
0626: name = feature.getName();
0627: nsBindings.put(name.getPrefix(), name
0628: .getNamespace());
0629: }
0630: }
0631: } else {
0632: // process contained features
0633: // for ( int i = 0; i < fc.size(); i++ ) {
0634: // name = fc.getFeature( i ).getName();
0635: // nsBindings.put( name.getPrefix(), name.getNamespace() );
0636: // }
0637: for (int i = 0; i < fc.size(); i++) {
0638: Feature feature = fc.getFeature(i);
0639: if (feature != null) {
0640: nsBindings = determineUsedNSBindings(feature,
0641: nsBindings);
0642: }
0643: }
0644: }
0645:
0646: return nsBindings;
0647: }
0648:
0649: /**
0650: * Determines the namespace bindings that are used in the feature and it's properties
0651: *
0652: * @param feature
0653: * feature
0654: * @param nsBindings
0655: * to add to.
0656: * @return the namespace bindings
0657: */
0658: private Map<String, URI> determineUsedNSBindings(Feature feature,
0659: Map<String, URI> nsBindings) {
0660: if (nsBindings == null) {
0661: nsBindings = new HashMap<String, URI>();
0662: }
0663: if (feature != null) {
0664: // process feature element
0665: QualifiedName qName = feature.getName();
0666: if (qName != null) {
0667: String prefix = qName.getPrefix();
0668: URI ns = qName.getNamespace();
0669: if (ns != null && !"".equals(ns.toASCIIString().trim())) {
0670: LOG.logDebug("Adding qName: " + qName);
0671: nsBindings.put(prefix, ns);
0672: }
0673: }
0674:
0675: // now check for properties which use a namespace
0676: FeatureProperty[] featureProperties = feature
0677: .getProperties();
0678: if (featureProperties != null) {
0679: for (FeatureProperty fp : featureProperties) {
0680: if (fp != null) {
0681: QualifiedName fpName = fp.getName();
0682: if (fpName != null) {
0683: String prefix = fpName.getPrefix();
0684: if (prefix != null
0685: && !"".equals(prefix.trim())) {
0686: if (nsBindings.get(prefix) == null) {
0687: URI ns = fpName.getNamespace();
0688: if (ns != null
0689: && !"".equals(ns
0690: .toASCIIString()
0691: .trim())) {
0692: LOG.logDebug("Adding qname: "
0693: + fpName);
0694: nsBindings.put(prefix, ns);
0695: }
0696: }
0697: }
0698: }
0699: // Object value = fp.getValue();
0700: // if ( value instanceof Feature ) {
0701: // determineUsedNSBindings( (Feature) value, nsBindings );
0702: // }
0703: }
0704: }
0705: }
0706: }
0707:
0708: return nsBindings;
0709: }
0710:
0711: /**
0712: * Determines the namespace bindings that are used in the feature.
0713: * <p>
0714: * NOTE: Currently only the bindings for the feature's root element and the contained features are considered. If a
0715: * subfeature uses another bindings, this binding will be missing in the XML.
0716: *
0717: * @param fc
0718: * feature
0719: * @return the namespace bindings
0720: */
0721: private Map<String, URI> determineUsedNSBindings(Feature feature) {
0722:
0723: // reset the counter.
0724: Map<String, URI> nsBindings = new HashMap<String, URI>();
0725:
0726: return determineUsedNSBindings(feature, nsBindings);
0727: // // process feature element
0728: // QualifiedName name = feature.getName();
0729: // nsBindings.put( name.getPrefix(), name.getNamespace() );
0730:
0731: }
0732:
0733: /**
0734: * Appends the given namespace bindings to the PrintWriter.
0735: *
0736: * @param bindings
0737: * namespace bindings to append
0738: * @param pw
0739: * PrintWriter to write to
0740: */
0741: private void appendNSBindings(Map<String, URI> bindings,
0742: PrintWriter pw) {
0743:
0744: Iterator<String> prefixIter = bindings.keySet().iterator();
0745: while (prefixIter.hasNext()) {
0746: String prefix = prefixIter.next();
0747: URI nsURI = bindings.get(prefix);
0748: if (prefix == null) {
0749: pw.print(" xmlns=\"");
0750: pw.print(nsURI);
0751: pw.print('\"');
0752: } else {
0753: pw.print(" xmlns:");
0754: pw.print(prefix);
0755: pw.print("=\"");
0756: pw.print(nsURI);
0757: pw.print('\"');
0758: }
0759: }
0760: // if more then one default namespaces were defined, each feature must (re)-determine the default ns.
0761: this .nsBindingsExported = true;// ( defaultNamespaceCounter == 0 );
0762: }
0763:
0764: /**
0765: * Exports an instance of a <code>Feature</code> to the passed <code>OutputStream</code> formatted as GML. Uses
0766: * the deegree system character set for the XML header encoding information.
0767: *
0768: * @param feature
0769: * feature to export
0770: * @param os
0771: * output stream to write to
0772: *
0773: * @throws IOException
0774: * @throws FeatureException
0775: */
0776: public void export(Feature feature, OutputStream os)
0777: throws IOException, FeatureException {
0778: export(feature, os, CharsetUtils.getSystemCharset());
0779: }
0780:
0781: /**
0782: * Exports a <code>Feature</code> instance to the passed <code>OutputStream</code> formatted as GML.
0783: *
0784: * @param feature
0785: * feature to export
0786: * @param os
0787: * output stream to write to
0788: * @param charsetName
0789: * name of the used charset/encoding (for the XML header)
0790: * @throws IOException
0791: * @throws FeatureException
0792: */
0793: public void export(Feature feature, OutputStream os,
0794: String charsetName) throws IOException, FeatureException {
0795:
0796: PrintWriter pw = new PrintWriter(new OutputStreamWriter(os,
0797: charsetName));
0798: pw.println("<?xml version=\"1.0\" encoding=\"" + charsetName
0799: + "\"?>");
0800: export(feature, pw);
0801: pw.close();
0802: }
0803:
0804: /**
0805: * Exports a <code>Feature</code> instance to the passed <code>PrintWriter</code> as GML.
0806: *
0807: * @param feature
0808: * feature to export
0809: * @param pw
0810: * PrintWriter to write to
0811: * @throws FeatureException
0812: */
0813: private void export(Feature feature, PrintWriter pw)
0814: throws FeatureException {
0815:
0816: QualifiedName ftName = feature.getName();
0817: String fid = feature.getId();
0818:
0819: if (this .suppressXLinkOutput && fid != null && !"".equals(fid)) {
0820: if (this .localFeatures.contains(fid)) {
0821: String msg = Messages
0822: .format("ERROR_CYLIC_FEATURE", fid);
0823: throw new FeatureException(msg);
0824: }
0825: this .localFeatures.add(fid);
0826: }
0827:
0828: // open feature element (add gml:id attribute if feature has an id)
0829: pw.print('<');
0830: pw.print(ftName.getPrefixedName());
0831: if (fid != null) {
0832: this .exportedFeatures.add(fid);
0833: pw.print(" gml:id=\"");
0834: pw.print(fid);
0835: pw.print('\"');
0836: }
0837:
0838: // determine and add namespace bindings
0839:
0840: if (!this .nsBindingsExported) {
0841:
0842: Map<String, URI> nsBindings = determineUsedNSBindings(feature);
0843: nsBindings.put("gml", CommonNamespaces.GMLNS);
0844: nsBindings.put("xlink", CommonNamespaces.XLNNS);
0845: if (this .schemaURL != null) {
0846: nsBindings.put("xsi", CommonNamespaces.XSINS);
0847: }
0848: appendNSBindings(nsBindings, pw);
0849: }
0850:
0851: pw.print('>');
0852:
0853: try {
0854: Envelope env = null;
0855: if ((env = feature.getBoundedBy()) != null) {
0856: pw.print("<gml:boundedBy><gml:Envelope");
0857: if (env.getCoordinateSystem() != null) {
0858: pw.print(" srsName='"
0859: + env.getCoordinateSystem()
0860: .getPrefixedName() + "'");
0861: }
0862: pw.print("><gml:pos srsDimension='2'>");
0863: pw.print(env.getMin().getX());
0864: pw.print(' ');
0865: pw.print(env.getMin().getY());
0866: pw.print("</gml:pos><gml:pos srsDimension=\"2\">");
0867: pw.print(env.getMax().getX());
0868: pw.print(' ');
0869: pw.print(env.getMax().getY());
0870: pw.print("</gml:pos></gml:Envelope></gml:boundedBy>");
0871: }
0872: } catch (GeometryException e) {
0873: LOG.logError(e.getMessage(), e);
0874: }
0875:
0876: // export all properties of the feature
0877: FeatureProperty[] properties = feature.getProperties();
0878: for (int i = 0; i < properties.length; i++) {
0879: if (properties[i] != null
0880: && properties[i].getValue() != null) {
0881: exportProperty(feature, properties[i], pw);
0882: }
0883: }
0884:
0885: // close feature element
0886: pw.print("</");
0887: pw.print(ftName.getPrefixedName());
0888: pw.println('>');
0889:
0890: if (this .suppressXLinkOutput || fid != null) {
0891: this .localFeatures.remove(fid);
0892: }
0893: }
0894:
0895: /**
0896: * Exports a <code>FeatureProperty</code> instance to the passed <code>PrintWriter</code> as GML.
0897: *
0898: * @param feature
0899: * feature that the property belongs to
0900: * @param property
0901: * property to export
0902: * @param pw
0903: * PrintWriter to write to
0904: * @throws FeatureException
0905: */
0906: private void exportProperty(Feature feature,
0907: FeatureProperty property, PrintWriter pw)
0908: throws FeatureException {
0909:
0910: QualifiedName propertyName = property.getName();
0911: Object value = property.getValue();
0912:
0913: if (value instanceof Feature) {
0914: Feature subfeature = (Feature) value;
0915:
0916: boolean isReferenceType = false;
0917: if (feature.getFeatureType() != null
0918: && feature.getFeatureType() instanceof MappedFeatureType) {
0919: MappedFeatureType ft = (MappedFeatureType) feature
0920: .getFeatureType();
0921: MappedFeaturePropertyType pt = (MappedFeaturePropertyType) ft
0922: .getProperty(property.getName());
0923: assert pt != null;
0924: isReferenceType = pt.isReferenceType();
0925: }
0926:
0927: if (isReferenceType
0928: || (exportedFeatures.contains(subfeature.getId()) && !this .suppressXLinkOutput)) {
0929: pw.print('<');
0930: pw.print(propertyName.getPrefixedName());
0931: pw.print(" xlink:href=\"#");
0932: pw.print(subfeature.getId());
0933: pw.print("\"/>");
0934: } else {
0935: pw.print('<');
0936: pw.print(propertyName.getPrefixedName());
0937: pw.print('>');
0938: exportPropertyValue(subfeature, pw);
0939: pw.print("</");
0940: pw.print(propertyName.getPrefixedName());
0941: pw.print('>');
0942: }
0943: } else {
0944: pw.print('<');
0945: pw.print(propertyName.getPrefixedName());
0946: pw.print('>');
0947: if (value != null) {
0948: FeatureType ft = feature.getFeatureType();
0949: PropertyType pt = ft.getProperty(property.getName());
0950: if (pt.getType() == Types.ANYTYPE) {
0951: pw.print(value);
0952: } else {
0953: exportPropertyValue(value, pw);
0954: }
0955: }
0956: pw.print("</");
0957: pw.print(propertyName.getPrefixedName());
0958: pw.print('>');
0959: }
0960: }
0961:
0962: /**
0963: * Exports the value of a property to the passed <code>PrintWriter</code> as GML.
0964: *
0965: * TODO make available and use property type information to determine correct output format (e.g. xs:date, xs:time,
0966: * xs:dateTime are all represented using Date objects and cannot be differentiated at the moment)
0967: *
0968: * @param value
0969: * property value to export
0970: * @param pw
0971: * PrintWriter to write to
0972: * @throws FeatureException
0973: */
0974: private void exportPropertyValue(Object value, PrintWriter pw)
0975: throws FeatureException {
0976: if (value instanceof Feature) {
0977: export((Feature) value, pw);
0978: } else if (value instanceof Feature[]) {
0979: Feature[] features = (Feature[]) value;
0980: for (int i = 0; i < features.length; i++) {
0981: export(features[i], pw);
0982: }
0983: } else if (value instanceof Envelope) {
0984: exportEnvelope((Envelope) value, pw);
0985: } else if (value instanceof FeatureCollection) {
0986: export((FeatureCollection) value, pw);
0987: } else if (value instanceof Geometry) {
0988: exportGeometry((Geometry) value, pw);
0989: } else if (value instanceof Date) {
0990:
0991: // TODO: use (currently unavailable) property type information to determine correct
0992: // output format (e.g. xs:date, xs:time, xs:dateTime are all represented using Date
0993: // objects and cannot be differentiated at the moment)
0994:
0995: // pw.print( ( (Date) value ).toString() );
0996: pw.print(TimeTools.getISOFormattedTime((Date) value));
0997: } else if (value instanceof Calendar) {
0998: pw.print(TimeTools.getISOFormattedTime((Calendar) value));
0999: } else if (value instanceof Timestamp) {
1000: pw.print(TimeTools.getISOFormattedTime((Timestamp) value));
1001: } else if (value instanceof java.sql.Date) {
1002: pw.print(TimeTools
1003: .getISOFormattedTime((java.sql.Date) value));
1004: } else if (value instanceof Integer || value instanceof Long
1005: || value instanceof Float || value instanceof Double
1006: || value instanceof BigDecimal) {
1007: pw.print(value.toString());
1008: } else if (value instanceof String) {
1009: StringBuffer sb = DOMPrinter.validateCDATA((String) value);
1010: pw.print(sb);
1011: } else if (value instanceof Boolean) {
1012: pw.print(value);
1013: } else {
1014: LOG.logInfo("Unhandled property class '" + value.getClass()
1015: + "' in GMLFeatureAdapter.");
1016: StringBuffer sb = DOMPrinter
1017: .validateCDATA(value.toString());
1018: pw.print(sb);
1019: }
1020: }
1021:
1022: /**
1023: * prints the passed geometry to the also passed PrintWriter formatted as GML
1024: *
1025: * @param geo
1026: * geometry to print/extport
1027: * @param pw
1028: * target of the printing/export
1029: * @throws FeatureException
1030: */
1031: private void exportGeometry(Geometry geo, PrintWriter pw)
1032: throws FeatureException {
1033: try {
1034: pw.print(GMLGeometryAdapter.export(geo));
1035: } catch (Exception e) {
1036: LOG.logError("", e);
1037: throw new FeatureException(
1038: "Could not export geometry to GML: "
1039: + e.getMessage(), e);
1040: }
1041: }
1042:
1043: /**
1044: * prints the passed geometry to the also passed PrintWriter formatted as GML
1045: *
1046: * @param geo
1047: * geometry to print/extport
1048: * @param pw
1049: * target of the printing/export
1050: * @throws FeatureException
1051: */
1052: private void exportEnvelope(Envelope geo, PrintWriter pw)
1053: throws FeatureException {
1054: try {
1055: pw.print(GMLGeometryAdapter.exportAsBox(geo));
1056: } catch (Exception e) {
1057: throw new FeatureException(
1058: "Could not export envelope to GML: "
1059: + e.getMessage(), e);
1060: }
1061: }
1062: }
|