    001    //$HeadURL: $
    002    /*----------------------------------------------------------------------------
    003     This file is part of deegree, http://deegree.org/
    004     Copyright (C) 2001-2009 by:
    005       Department of Geography, University of Bonn
    006     and
    007       lat/lon GmbH
    008
    009     This library is free software; you can redistribute it and/or modify it under
    010     the terms of the GNU Lesser General Public License as published by the Free
    011     Software Foundation; either version 2.1 of the License, or (at your option)
    012     any later version.
    013     This library is distributed in the hope that it will be useful, but WITHOUT
    014     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    015     FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
    016     details.
    017     You should have received a copy of the GNU Lesser General Public License
    018     along with this library; if not, write to the Free Software Foundation, Inc.,
    019     59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
    020
    021     Contact information:
    022
    023     lat/lon GmbH
    024     Aennchenstr. 19, 53177 Bonn
    025     Germany
    026     http://lat-lon.de/
    027
    028     Department of Geography, University of Bonn
    029     Prof. Dr. Klaus Greve
    030     Postfach 1147, 53001 Bonn
    031     Germany
    032     http://www.geographie.uni-bonn.de/deegree/
    033
    034     e-mail: info@deegree.org
    035    ----------------------------------------------------------------------------*/
    036
    037    package org.deegree.ogcwebservices.wcts;
    038
    039    import static org.deegree.framework.xml.XMLTools.appendElement;
    040    import static org.deegree.ogcbase.CommonNamespaces.DEEGREEWCTS;
    041    import static org.deegree.ogcbase.CommonNamespaces.DEEGREEWCTS_PREFIX;
    042    import static org.deegree.ogcbase.CommonNamespaces.OWSNS_1_1_0;
    043    import static org.deegree.ogcbase.CommonNamespaces.WCTSNS;
    044    import static org.deegree.ogcbase.CommonNamespaces.WCTS_PREFIX;
    045    import static org.deegree.ogcbase.CommonNamespaces.XLINK_PREFIX;
    046    import static org.deegree.ogcbase.CommonNamespaces.XLNNS;
    047    import static org.deegree.ogcwebservices.wcts.operation.Transform.INLINE;
    048    import static org.deegree.ogcwebservices.wcts.operation.Transform.MULTIPART;
    049
    050    import java.io.IOException;
    051    import java.util.List;
    052    import java.util.Map;
    053
    054    import javax.vecmath.Point3d;
    055
    056    import org.deegree.crs.transformations.Transformation;
    057    import org.deegree.framework.log.ILogger;
    058    import org.deegree.framework.log.LoggerFactory;
    059    import org.deegree.framework.util.Pair;
    060    import org.deegree.framework.xml.XMLFragment;
    061    import org.deegree.framework.xml.XMLTools;
    062    import org.deegree.model.crs.CoordinateSystem;
    063    import org.deegree.model.feature.FeatureCollection;
    064    import org.deegree.model.feature.FeatureException;
    065    import org.deegree.model.feature.GMLFeatureAdapter;
    066    import org.deegree.model.spatialschema.GMLGeometryAdapter;
    067    import org.deegree.model.spatialschema.Geometry;
    068    import org.deegree.model.spatialschema.GeometryException;
    069    import org.deegree.ogcwebservices.wcts.capabilities.Content;
    070    import org.deegree.ogcwebservices.wcts.capabilities.CoverageAbilities;
    071    import org.deegree.ogcwebservices.wcts.capabilities.FeatureAbilities;
    072    import org.deegree.ogcwebservices.wcts.capabilities.InputOutputFormat;
    073    import org.deegree.ogcwebservices.wcts.capabilities.WCTSCapabilities;
    074    import org.deegree.ogcwebservices.wcts.capabilities.mdprofiles.MetadataProfile;
    075    import org.deegree.ogcwebservices.wcts.capabilities.mdprofiles.TransformationMetadata;
    076    import org.deegree.ogcwebservices.wcts.data.FeatureCollectionData;
    077    import org.deegree.ogcwebservices.wcts.data.GeometryData;
    078    import org.deegree.ogcwebservices.wcts.data.SimpleData;
    079    import org.deegree.ogcwebservices.wcts.data.TransformableData;
    080    import org.deegree.ogcwebservices.wcts.operation.GetResourceByID;
    081    import org.deegree.ogcwebservices.wcts.operation.TransformResponse;
    082    import org.deegree.owscommon_1_1_0.Manifest;
    083    import org.deegree.owscommon_1_1_0.Metadata;
    084    import org.w3c.dom.Document;
    085    import org.w3c.dom.Element;
    086    import org.w3c.dom.Node;
    087    import org.xml.sax.SAXException;
    088
    089    /**
    090     * The <code>XMLFactory</code> provides helper methods to create xml-doc representations of bean encapsulations.
    091     *
    092     * @author <a href="mailto:bezema@lat-lon.de">Rutger Bezema</a>
    093     *
    094     * @author last edited by: $Author:$
    095     *
    096     * @version $Revision:$, $Date:$
    097     *
    098     */
    099    public class XMLFactory {
    100
    101        private static ILogger LOG = LoggerFactory.getLogger( XMLFactory.class );
    102
    103        private static final String PRE = WCTS_PREFIX + ":";
    104
    105        private static XMLFragment capabilitiesElement = null;
    106
    107        private static final String MUTEX = "EMPTY";
    108
    109        /**
    110         * Exports an GetResourceById bean to xml.
    111         *
    112         * @param resourceByID
    113         * @return a dom-representation.
    114         */
    115        public static XMLFragment create( GetResourceByID resourceByID ) {
    116            return null;
    117        }
    118
    119        /**
    120         * Exports an WCTSCapabilies bean to xml.
    121         *
    122         * @param capabilities
    123         *            to be exported.
    124         * @return an xml-dom-representation of the given bean or <code>null</code> if the given parameter is
    125         *         <code>null</code>.
    126         *
    127         */
    128        public static XMLFragment create( WCTSCapabilities capabilities ) {
    129            if ( capabilities == null ) {
    130                return null;
    131            }
    132            if ( capabilitiesElement == null ) {
    133                synchronized ( MUTEX ) {
    134                    if ( capabilitiesElement == null ) {
    135                        org.deegree.owscommon_1_1_0.XMLFactory fac = new org.deegree.owscommon_1_1_0.XMLFactory();
    136                        Document doc = XMLTools.create();
    137                        Element root = doc.createElementNS( WCTSNS.toASCIIString(), PRE + "Capabilities" );
    138                        capabilitiesElement = new XMLFragment( root );
    139                        fac.exportCapabilities( root, capabilities );
    140                        Content content = capabilities.getContents();
    141                        if ( content != null ) {
    142                            appendCapabilitiesContent( root, content );
    143                        }
    144                    }
    145                    try {
    146                        MUTEX.notifyAll();
    147                    } catch ( IllegalMonitorStateException e ) {
    148                        // nottin
    149                    }
    150                }
    151            }
    152            return capabilitiesElement;
    153        }
    154
    155        /**
    156         * Creates a response to a Transform request. The wcts spec defines it to be a ows_1_1_0:OperationResponse, this
    157         * method appends the deegreewcts:MultiParts or the deegreewcts:InlineData element(s) to the root node.
    158         *
    159         * @param transformResponse
    160         *            to create.
    161         * @param useDeegreeModel
    162         *            true if the transform response element should be embedded into inline/multipart elements.
    163         * @return the ows_1_1_0:OperationResponse with deegreewcts:MultiPart element added or <code>null</code> if the
    164         *         given param is <code>null</code>.
    165         */
    166        public static XMLFragment createResponse( TransformResponse transformResponse, boolean useDeegreeModel ) {
    167            if ( transformResponse == null ) {
    168                return null;
    169            }
    170            org.deegree.owscommon_1_1_0.XMLFactory owsFac = new org.deegree.owscommon_1_1_0.XMLFactory();
    171            Element dataElement = null;
    172            LOG.logDebug( "Creating tranform operation response." );
    173            Document doc = XMLTools.create();
    174            Element root = doc.createElementNS( DEEGREEWCTS.toASCIIString(), PRE + "OperationResponse" );
    175            XMLFragment result = new XMLFragment( root );
    176            createOperationResponse( owsFac, result.getRootElement(), transformResponse.getInputData() );
    177            if ( useDeegreeModel ) {
    178                switch ( transformResponse.getDataPresentation() ) {
    179                case INLINE:
    180                    LOG.logDebug( "Creating tranform operation inline data response." );
    181                    dataElement = appendElement( result.getRootElement(), DEEGREEWCTS, DEEGREEWCTS_PREFIX + ":InlineData" );
    182                    break;
    183                case MULTIPART: // fall through
    184                default:
    185                    LOG.logDebug( "Creating tranform operation multipart data response." );
    186                    // result = owsFac.createOperationResponse( transformResponse.getInputData() );
    187                    dataElement = appendElement( result.getRootElement(), DEEGREEWCTS, DEEGREEWCTS_PREFIX + ":MultiParts" );
    188                    break;
    189                }
    190            } else {
    191                dataElement = root;
    192            }
    193            appendTransformableData( dataElement, transformResponse, useDeegreeModel );
    194            return result;
    195        }
    196
    197        /**
    198         * Will create an XMLFragment which holds the d_wcts:OperationResponse as the root element, values from the given
    199         * manifest will be appended by the given ows_1_1 XMLFactory.
    200         *
    201         * @param owsFactory
    202         *            an instance of the ows_1-1 XMLFactory
    203         * @param root
    204         *            to append the manifest to.
    205         *
    206         * @param operationResponse
    207         *            to create the dom-xml representation from.
    208         */
    209        public static void createOperationResponse( org.deegree.owscommon_1_1_0.XMLFactory owsFactory, Element root,
    210                                                    Manifest operationResponse ) {
    211            if ( operationResponse == null ) {
    212                return;
    213            }
    214            // Document doc = XMLTools.create();
    215            // Element root = doc.createElementNS( DEEGREEWCTS.toASCIIString(), PRE + "OperationResponse" );
    216            owsFactory.appendManifest( root, operationResponse );
    217            // return new XMLFragment( root );
    218        }
    219
    220        /**
    221         * Appends the TransformableData bean, as an xml-dom element to the given root. If either one of the parameters is
    222         * <code>null</code>, this method just returns.
    223         *
    224         * @param root
    225         *            to append to.
    226         * @param response
    227         *            to get the transformable data from.
    228         * @param useDeegreeModel
    229         *            true if the transform response element should be embedded into inline/multipart elements.
    230         */
    231        protected static void appendTransformableData( Element root, TransformResponse response, boolean useDeegreeModel ) {
    232            if ( root == null || response == null ) {
    233                return;
    234            }
    235
    236            TransformableData<?> transformableData = response.getTransformableData();
    237            LOG.logDebug( "Appending transformable data. " );
    238            if ( transformableData instanceof SimpleData ) {
    239                appendSimpleData( root, response.getTargetCRS().getDimension(), (SimpleData) transformableData,
    240                                  useDeegreeModel );
    241            } else if ( transformableData instanceof GeometryData ) {
    242                appendGeometryData( root, (GeometryData) transformableData, useDeegreeModel );
    243            } else if ( transformableData instanceof FeatureCollectionData ) {
    244                appendFeatureCollectionData( root, (FeatureCollectionData) transformableData, useDeegreeModel );
    245            }
    246        }
    247
    248        /**
    249         * Appends a dom-xml document element with the name {http://www.deegree.org/wcts}:FeatureCollectionData. It will
    250         * contain all transformed FeaturCollection as it's children. Or if no FeatureCollections were transformed this
    251         * element will have no children at all.
    252         * <p>
    253         * If either one of the parameters is <code>null</code>, this method just returns.
    254         * </p>
    255         *
    256         * @param root
    257         *            to append to.
    258         * @param transformableData
    259         *            to append.
    260         * @param useDeegreeModel
    261         *            true if the transform response element should be embedded into inline/multipart elements.
    262         *
    263         */
    264        protected static void appendFeatureCollectionData( Element root, FeatureCollectionData transformableData,
    265                                                           boolean useDeegreeModel ) {
    266            if ( root == null || transformableData == null ) {
    267                return;
    268            }
    269            LOG.logDebug( "Adding tranformed feature collection data." );
    270            Element featureCollectionElement = null;
    271            if ( useDeegreeModel ) {
    272                featureCollectionElement = appendElement( root, DEEGREEWCTS, DEEGREEWCTS_PREFIX + ":FeatureCollectionData" );
    273            } else {
    274                featureCollectionElement = root;
    275            }
    276            GMLFeatureAdapter ad = new GMLFeatureAdapter();
    277            List<FeatureCollection> transformedData = transformableData.getTransformedData();
    278            if ( transformedData != null && transformedData.size() >= 0 ) {
    279                for ( FeatureCollection featureCollection : transformedData ) {
    280                    if ( featureCollection != null ) {
    281                        try {
    282                            ad.append( featureCollectionElement, featureCollection );
    283                        } catch ( FeatureException e ) {
    284                            LOG.logError( e.getMessage(), e );
    285                        } catch ( IOException e ) {
    286                            LOG.logError( e.getMessage(), e );
    287                        } catch ( SAXException e ) {
    288                            LOG.logError( e.getMessage(), e );
    289                        }
    290                    }
    291                }
    292            }
    293        }
    294
    295        /**
    296         * Appends a dom-xml document element with the name {http://www.deegree.org/wcts}:GeometryData. It it will contain
    297         * all transformed Geometries as it's children. Or if no Geometries were transformed this element will have no
    298         * children at all.
    299         * <p>
    300         * If either one of the parameters is <code>null</code>, this method just returns.
    301         * </p>
    302         *
    303         * @param root
    304         *            to append to.
    305         * @param transformableData
    306         *            to append.
    307         * @param useDeegreeModel
    308         *            true if the transform response element should be embedded into inline/multipart elements.
    309         *
    310         */
    311        protected static void appendGeometryData( Element root, GeometryData transformableData, boolean useDeegreeModel ) {
    312            if ( root == null || transformableData == null ) {
    313                return;
    314            }
    315            LOG.logDebug( "Adding tranformed geometry data." );
    316            Element geometryElement = null;
    317            if ( useDeegreeModel ) {
    318                geometryElement = appendElement( root, DEEGREEWCTS, DEEGREEWCTS_PREFIX + ":GeometryData" );
    319            } else {
    320                geometryElement = root;
    321            }
    322            Document doc = geometryElement.getOwnerDocument();
    323            List<Geometry> transformedGeometries = transformableData.getTransformedData();
    324            if ( transformedGeometries.size() >= 0 ) {
    325                for ( Geometry geom : transformedGeometries ) {
    326                    if ( geom != null ) {
    327                        try {
    328                            StringBuffer sb = GMLGeometryAdapter.export( geom );
    329                            Element tmp = XMLTools.getStringFragmentAsElement( sb.toString() );
    330                            if ( tmp != null ) {
    331                                tmp = (Element) doc.importNode( tmp, true );
    332                                geometryElement.appendChild( tmp );
    333                            }
    334                        } catch ( GeometryException e ) {
    335                            LOG.logError( e.getMessage(), e );
    336                        } catch ( SAXException e ) {
    337                            LOG.logError( e.getMessage(), e );
    338                        } catch ( IOException e ) {
    339                            LOG.logError( e.getMessage(), e );
    340                        }
    341                    }
    342                }
    343            }
    344        }
    345
    346        /**
    347         * Appends a dom-xml document element with the name is {http://www.deegree.org/wcts}:SimpleData. It will contain the
    348         * points as a separated list as defined by the 'cs' separator. The element has the attribute 'srsDimension'. The
    349         * list elements can therefore be interpreted as a tuple of the value of 'srsDimension'. If no points were
    350         * transformed the list will be empty.
    351         * <p>
    352         * If either one of the parameters is <code>null</code>, this method just returns.
    353         * </p>
    354         *
    355         * @param root
    356         *            to append to.
    357         * @param targetDimension
    358         *            of the target CRS
    359         * @param transformableData
    360         *            to append.
    361         * @param useDeegreeModel
    362         *            true if the transform response element should be embedded into inline/multipart elements.
    363         *
    364         */
    365        protected static void appendSimpleData( Element root, int targetDimension, SimpleData transformableData,
    366                                                boolean useDeegreeModel ) {
    367            if ( root == null || transformableData == null ) {
    368                return;
    369            }
    370            LOG.logDebug( "Adding tranformed simple data." );
    371            Element simpleDataElement = null;
    372            if ( useDeegreeModel ) {
    373                simpleDataElement = appendElement( root, DEEGREEWCTS, DEEGREEWCTS_PREFIX + ":SimpleData" );
    374            } else {
    375                simpleDataElement = root;
    376            }
    377            int dim = targetDimension;
    378            final String ts = transformableData.getTupleSeparator();
    379            simpleDataElement.setAttribute( "ts", ts );
    380            final String cs = transformableData.getCoordinateSeparator();
    381            simpleDataElement.setAttribute( "cs", cs );
    382            List<Point3d> transformedPoints = transformableData.getTransformedData();
    383            StringBuilder sb = new StringBuilder( transformedPoints.size() * dim );
    384            for ( int i = 0; i < transformedPoints.size(); ++i ) {
    385                Point3d point = transformedPoints.get( i );
    386                if ( point != null ) {
    387                    sb.append( point.x );
    388                    sb.append( cs );
    389                    sb.append( point.y );
    390                    if ( dim == 3 ) {
    391                        sb.append( cs );
    392                        sb.append( point.z );
    393                    }
    394                    if ( ( i + 1 ) < transformedPoints.size() ) {
    395                        sb.append( ts );
    396                    }
    397                }
    398            }
    399            XMLTools.setNodeValue( simpleDataElement, sb.toString() );
    400        }
    401
    402        /**
    403         * Appends the WCTSContent bean, as an xml-dom element to the given root. If either one of the parameters is
    404         * <code>null</code>, this method just returns.
    405         *
    406         * @param root
    407         *            to append the values to.
    408         * @param content
    409         *            to be appended.
    410         */
    411        protected static void appendCapabilitiesContent( Element root, Content content ) {
    412            if ( content == null || root == null ) {
    413                return;
    414            }
    415            Element contentElement = appendElement( root, WCTSNS, PRE + "Contents" );
    416            Map<String, Transformation> transformations = content.getTransformations();
    417            if ( transformations != null && transformations.size() > 0 ) {
    418                for ( String transform : transformations.keySet() ) {
    419                    if ( transform != null ) {
    420                        appendElement( contentElement, WCTSNS, PRE + "Transformation", transform );
    421                    }
    422                }
    423            }
    424
    425            List<String> methods = content.getMethods();
    426            if ( methods != null && methods.size() > 0 ) {
    427                for ( String s : methods ) {
    428                    if ( s != null ) {
    429                        appendElement( contentElement, WCTSNS, PRE + "Method", s );
    430                    }
    431                }
    432            }
    433
    434            List<CoordinateSystem> sourceCRSs = content.getSourceCRSs();
    435            if ( sourceCRSs != null && sourceCRSs.size() > 0 ) {
    436                for ( CoordinateSystem s : sourceCRSs ) {
    437                    if ( s != null ) {
    438                        appendElement( contentElement, WCTSNS, PRE + "SourceCRS", s.getIdentifier() );
    439                    }
    440                }
    441            }
    442
    443            List<CoordinateSystem> targetCRSs = content.getTargetCRSs();
    444            if ( targetCRSs != null && targetCRSs.size() > 0 ) {
    445                for ( CoordinateSystem s : targetCRSs ) {
    446                    if ( s != null ) {
    447                        appendElement( contentElement, WCTSNS, PRE + "TargetCRS", s.getIdentifier() );
    448                    }
    449                }
    450            }
    451
    452            CoverageAbilities cAbilities = content.getCoverageAbilities();
    453            if ( cAbilities != null ) {
    454                Element caElement = appendElement( contentElement, WCTSNS, PRE + "CoverageAbilities" );
    455                List<Pair<String, String>> coverageTypes = cAbilities.getCoverageTypes();
    456                if ( coverageTypes != null && coverageTypes.size() > 0 ) {
    457                    for ( Pair<String, String> values : coverageTypes ) {
    458                        if ( values != null ) {
    459                            Element ctElement = appendElement( caElement, WCTSNS, PRE + "CoverageType", values.first );
    460                            ctElement.setAttribute( "codeSpace", values.second );
    461                        }
    462                    }
    463                }
    464                List<InputOutputFormat> cFormats = cAbilities.getCoverageFormats();
    465                if ( cFormats != null && cFormats.size() > 0 ) {
    466                    for ( InputOutputFormat iof : cFormats ) {
    467                        appendInputOutput( caElement, iof, "CoverageFormat" );
    468                    }
    469                }
    470                List<Element> interpolationMethods = cAbilities.getInterPolationMethods();
    471                if ( interpolationMethods != null && interpolationMethods.size() > 0 ) {
    472                    for ( Element element : interpolationMethods ) {
    473                        if ( element != null ) {
    474                            Element copyOf = (Element) caElement.getOwnerDocument().importNode( element, true );
    475                            caElement.appendChild( copyOf );
    476                        }
    477                    }
    478                }
    479
    480            }
    481            FeatureAbilities fAbilities = content.getFeatureAbilities();
    482            if ( fAbilities != null ) {
    483                Element faElement = appendElement( contentElement, WCTSNS, PRE + "FeatureAbilities" );
    484                List<Pair<String, String>> geometryTypes = fAbilities.getGeometryTypes();
    485                if ( geometryTypes != null && geometryTypes.size() > 0 ) {
    486                    for ( Pair<String, String> values : geometryTypes ) {
    487                        if ( values != null ) {
    488                            Element ctElement = appendElement( faElement, WCTSNS, PRE + "GeometryType", values.first );
    489                            ctElement.setAttribute( "codeSpace", values.second );
    490                        }
    491                    }
    492                }
    493                List<InputOutputFormat> fFormats = fAbilities.getFeatureFormats();
    494                if ( fFormats != null && fFormats.size() > 0 ) {
    495                    for ( InputOutputFormat iof : fFormats ) {
    496                        appendInputOutput( faElement, iof, "FeatureFormat" );
    497                    }
    498                }
    499                faElement.setAttribute( "remoteProperties", fAbilities.getRemoteProperties() ? "true" : "false" );
    500            }
    501
    502            List<Metadata> metadataList = content.getMetadata();
    503            if ( metadataList != null && metadataList.size() > 0 ) {
    504                for ( Metadata attrib : metadataList ) {
    505                    if ( attrib != null ) {
    506                        Element mdElement = createMetadataRoot( contentElement, attrib );
    507                        if ( mdElement != null ) {
    508                            Element abst = attrib.getAbstractElement();
    509                            if ( abst != null ) {
    510                                Node n = mdElement.getOwnerDocument().importNode( abst, true );
    511                                mdElement.appendChild( n );
    512                            }
    513                        }
    514                    }
    515                }
    516            }
    517            if ( content.getTransformMetadata() != null ) {
    518                for ( MetadataProfile<?> mp : content.getTransformMetadata() ) {
    519                    if ( mp != null && ( mp instanceof TransformationMetadata ) ) {
    520                        Element metadata = appendElement( contentElement, OWSNS_1_1_0, PRE + "Metadata" );
    521                        TransformationMetadata tmd = (TransformationMetadata) mp;
    522                        Element tmElem = appendElement( metadata, DEEGREEWCTS, DEEGREEWCTS_PREFIX
    523                                                                               + ":transformationMetadata" );
    524                        tmElem.setAttribute( "sourceCRS", tmd.getSourceCRS().getIdentifier() );
    525                        tmElem.setAttribute( "targetCRS", tmd.getTargetCRS().getIdentifier() );
    526                        tmElem.setAttribute( "transformationID", tmd.getTransformID() );
    527                        String desc = tmd.getDescription();
    528                        if ( desc == null || "".equals( desc ) ) {
    529                            desc = "No description";
    530                        }
    531                        Element description = appendElement( tmElem, DEEGREEWCTS, DEEGREEWCTS_PREFIX + ":description" );
    532                        XMLTools.setNodeValue( description, desc );
    533                    }
    534                }
    535            }
    536
    537            contentElement.setAttribute( "userDefinedCRSs", content.supportsUserDefinedCRS() ? "true" : "false" );
    538        }
    539
    540        private static Element createMetadataRoot( Element root, Metadata md ) {
    541            if ( root == null || md == null ) {
    542                return null;
    543            }
    544            Element metadata = appendElement( root, OWSNS_1_1_0, PRE + "Metadata" );
    545            if ( md.getMetadataHref() != null ) {
    546                metadata.setAttributeNS( XLNNS.toASCIIString(), XLINK_PREFIX + ":href", md.getMetadataHref() );
    547            }
    548            if ( md.getMetadataAbout() != null ) {
    549                metadata.setAttribute( "about", md.getMetadataAbout() );
    550            }
    551            return metadata;
    552        }
    553
    554        /**
    555         * Appends the input output element
    556         *
    557         * @param root
    558         *            to append to
    559         * @param inputOutput
    560         *            to append
    561         * @param nodeName
    562         *            the element name, i.e. CoverageFormat or FeatureFormat
    563         */
    564        private static void appendInputOutput( Element root, InputOutputFormat inputOutput, String nodeName ) {
    565            if ( inputOutput != null && root != null ) {
    566                Element cfElement = appendElement( root, WCTSNS, PRE + nodeName, inputOutput.getValue() );
    567                cfElement.setAttribute( "input", inputOutput.canInput() ? "true" : "false" );
    568                cfElement.setAttribute( "output", inputOutput.canOutput() ? "true" : "false" );
    569
    570            }
    571        }
    572    }