/**
     * Encodes contact information in the WMS capabilities document
     *
     * @param geoServer
     */
    public void handleContactInfo(ContactInfo contact) {
      start("ContactInformation");

      start("ContactPersonPrimary");
      element("ContactPerson", contact.getContactPerson());
      element("ContactOrganization", contact.getContactOrganization());
      end("ContactPersonPrimary");

      element("ContactPosition", contact.getContactPosition());

      start("ContactAddress");
      element("AddressType", contact.getAddressType());
      element("Address", contact.getAddress());
      element("City", contact.getAddressCity());
      element("StateOrProvince", contact.getAddressState());
      element("PostCode", contact.getAddressPostalCode());
      element("Country", contact.getAddressCountry());
      end("ContactAddress");

      element("ContactVoiceTelephone", contact.getContactVoice());
      element("ContactFacsimileTelephone", contact.getContactFacsimile());
      element("ContactElectronicMailAddress", contact.getContactEmail());

      end("ContactInformation");
    }
    protected void handleAttribution(LayerInfo layer) {
      AttributionInfo attribution = layer.getAttribution();

      String title = attribution.getTitle();
      String url = attribution.getHref();
      String logoURL = attribution.getLogoURL();
      String logoType = attribution.getLogoType();
      int logoWidth = attribution.getLogoWidth();
      int logoHeight = attribution.getLogoHeight();

      boolean titleGood = (title != null),
          urlGood = (url != null),
          logoGood = (logoURL != null && logoType != null && logoWidth > 0 && logoHeight > 0);

      if (titleGood || urlGood || logoGood) {
        start("Attribution");
        if (titleGood) element("Title", title);

        if (urlGood) {
          AttributesImpl urlAttributes = new AttributesImpl();
          urlAttributes.addAttribute("", "xmlns:xlink", "xmlns:xlink", "", XLINK_NS);
          urlAttributes.addAttribute(XLINK_NS, "type", "xlink:type", "", "simple");
          urlAttributes.addAttribute(XLINK_NS, "href", "xlink:href", "", url);
          element("OnlineResource", null, urlAttributes);
        }

        if (logoGood) {
          AttributesImpl logoAttributes = new AttributesImpl();
          logoAttributes.addAttribute("", "", "height", "", "" + logoHeight);
          logoAttributes.addAttribute("", "", "width", "", "" + logoWidth);
          start("LogoURL", logoAttributes);
          element("Format", logoType);

          AttributesImpl urlAttributes = new AttributesImpl();
          urlAttributes.addAttribute("", "xmlns:xlink", "xmlns:xlink", "", XLINK_NS);
          urlAttributes.addAttribute(XLINK_NS, "type", "xlink:type", "", "simple");
          urlAttributes.addAttribute(XLINK_NS, "href", "xlink:href", "", logoURL);

          element("OnlineResource", null, urlAttributes);
          end("LogoURL");
        }

        end("Attribution");
      }
    }
 /** Encodes the capabilities metadata section of a WMS capabilities document */
 private void handleCapability() {
   start("Capability");
   handleRequest();
   handleException();
   handleVendorSpecificCapabilities();
   handleSLD();
   handleLayers();
   end("Capability");
 }
    private void handleException() {
      start("Exception");

      for (String exceptionFormat : GetCapabilitiesTransformer.EXCEPTION_FORMATS) {
        element("Format", exceptionFormat);
      }

      end("Exception");
    }
    /**
     * Turns the keyword list to XML
     *
     * @param keywords
     */
    private void handleKeywordList(List<String> keywords) {
      start("KeywordList");

      if (keywords != null) {
        for (Iterator<String> it = keywords.iterator(); it.hasNext(); ) {
          element("Keyword", it.next());
        }
      }

      end("KeywordList");
    }
    /** @param layerTree */
    private void handleLayerTree(final LayerTree layerTree) {
      final List<LayerInfo> data = new ArrayList<LayerInfo>(layerTree.getData());
      final Collection<LayerTree> children = layerTree.getChildrens();

      Collections.sort(
          data,
          new Comparator<LayerInfo>() {
            public int compare(LayerInfo o1, LayerInfo o2) {
              return o1.getName().compareTo(o2.getName());
            }
          });

      for (LayerInfo layer : data) {
        // no sense in exposing a geometryless layer through wms...
        boolean wmsExposable = false;
        if (layer.getType() == Type.RASTER || layer.getType() == Type.WMS) {
          wmsExposable = true;
        } else {
          try {
            wmsExposable =
                layer.getType() == Type.VECTOR
                    && ((FeatureTypeInfo) layer.getResource())
                            .getFeatureType()
                            .getGeometryDescriptor()
                        != null;
          } catch (Exception e) {
            LOGGER.log(
                Level.SEVERE,
                "An error occurred trying to determine if" + " the layer is geometryless",
                e);
          }
        }

        // ask for enabled() instead of isEnabled() to account for disabled resource/store
        if (layer.enabled() && wmsExposable) {
          try {
            handleLayer(layer);
          } catch (Exception e) {
            // report what layer we failed on to help the admin locate and fix it
            throw new ServiceException(
                "Error occurred trying to write out metadata for layer: " + layer.getName(), e);
          }
        }
      }

      for (LayerTree childLayerTree : children) {
        start("Layer");
        element("Name", childLayerTree.getName());
        element("Title", childLayerTree.getName());
        handleLayerTree(childLayerTree);
        end("Layer");
      }
    }
    /**
     * Handles the encoding of the layers elements.
     *
     * <p>This method does a search over the SRS of all the layers to see if there are at least a
     * common one, as needed by the spec: "<i>The root Layer element shall include a sequence of
     * zero or more &lt;SRS&gt; elements listing all SRSes that are common to all subsidiary layers.
     * Use a single SRS element with empty content (like so: "&lt;SRS&gt;&lt;/SRS&gt;") if there is
     * no common SRS."</i>
     *
     * <p>By the other hand, this search is also used to collecto the whole latlon bbox, as stated
     * by the spec: <i>"The bounding box metadata in Capabilities XML specify the minimum enclosing
     * rectangle for the layer as a whole."</i>
     *
     * @task TODO: manage this differently when we have the layer list of the WMS service decoupled
     *     from the feature types configured for the server instance. (This involves nested layers,
     *     gridcoverages, etc)
     */
    private void handleLayers() {
      start("Layer");

      final List<LayerInfo> layers;

      // filter the layers if a namespace filter has been set
      if (request.getNamespace() != null) {
        final List<LayerInfo> allLayers = wmsConfig.getLayers();
        layers = new ArrayList<LayerInfo>();

        String namespace = wmsConfig.getNamespaceByPrefix(request.getNamespace());
        for (LayerInfo layer : allLayers) {
          Name name = layer.getResource().getQualifiedName();
          if (name.getNamespaceURI().equals(namespace)) {
            layers.add(layer);
          }
        }
      } else {
        layers = wmsConfig.getLayers();
      }

      WMSInfo serviceInfo = wmsConfig.getServiceInfo();
      element("Title", serviceInfo.getTitle());
      element("Abstract", serviceInfo.getAbstract());

      List<String> srsList = serviceInfo.getSRS();
      Set<String> srs = new HashSet<String>();
      if (srsList != null) {
        srs.addAll(srsList);
      }
      handleRootCrsList(srs);

      handleRootBbox(layers);

      // now encode each layer individually
      LayerTree featuresLayerTree = new LayerTree(layers);
      handleLayerTree(featuresLayerTree);

      try {
        List<LayerGroupInfo> layerGroups = wmsConfig.getLayerGroups();
        handleLayerGroups(new ArrayList<LayerGroupInfo>(layerGroups));
      } catch (FactoryException e) {
        throw new RuntimeException("Can't obtain Envelope of Layer-Groups: " + e.getMessage(), e);
      } catch (TransformException e) {
        throw new RuntimeException("Can't obtain Envelope of Layer-Groups: " + e.getMessage(), e);
      }

      end("Layer");
    }
    /**
     * Encodes a <code>DCPType</code> fragment for HTTP GET and POST methods.
     *
     * @param getUrl the URL of the onlineresource for HTTP GET method requests
     * @param postUrl the URL of the onlineresource for HTTP POST method requests
     */
    private void handleDcpType(String getUrl, String postUrl) {
      AttributesImpl orAtts = new AttributesImpl();
      orAtts.addAttribute("", "xmlns:xlink", "xmlns:xlink", "", XLINK_NS);
      orAtts.addAttribute("", "xlink:type", "xlink:type", "", "simple");
      orAtts.addAttribute("", "xlink:href", "xlink:href", "", getUrl);
      start("DCPType");
      start("HTTP");

      if (getUrl != null) {
        start("Get");
        element("OnlineResource", null, orAtts);
        end("Get");
      }

      if (postUrl != null) {
        orAtts.setAttribute(2, "", "xlink:href", "xlink:href", "", postUrl);
        start("Post");
        element("OnlineResource", null, orAtts);
        end("Post");
      }

      end("HTTP");
      end("DCPType");
    }
    private void handleVendorSpecificCapabilities() {
      /*
       * Check whether some caps provider contributes to the internal DTD. If not, there's no
       * need to output the VendorSpecificCapabilities element. Moreover, the document will
       * not validate if it's there but not declared in the internal DTD
       */
      for (ExtendedCapabilitiesProvider cp : extCapsProviders) {
        List<String> roots = cp.getVendorSpecificCapabilitiesRoots(request);
        if (roots == null || roots.size() == 0) {
          return;
        }
      }

      start("VendorSpecificCapabilities");
      for (ExtendedCapabilitiesProvider cp : extCapsProviders) {
        try {
          cp.encode(
              new ExtendedCapabilitiesProvider.Translator() {
                public void start(String element) {
                  CapabilitiesTranslator.this.start(element);
                }

                public void start(String element, Attributes attributes) {
                  CapabilitiesTranslator.this.start(element, attributes);
                }

                public void chars(String text) {
                  CapabilitiesTranslator.this.chars(text);
                }

                public void end(String element) {
                  CapabilitiesTranslator.this.end(element);
                }
              },
              wmsConfig.getServiceInfo(),
              request);
        } catch (Exception e) {
          throw new ServiceException("Extended capabilities provider threw error", e);
        }
      }
      end("VendorSpecificCapabilities");
    }
    /**
     * @param o the {@link GetCapabilitiesRequest}
     * @throws IllegalArgumentException if {@code o} is not of the expected type
     */
    public void encode(Object o) throws IllegalArgumentException {
      if (!(o instanceof GetCapabilitiesRequest)) {
        throw new IllegalArgumentException();
      }

      this.request = (GetCapabilitiesRequest) o;

      if (LOGGER.isLoggable(Level.FINE)) {
        LOGGER.fine(
            new StringBuffer("producing a capabilities document for ").append(request).toString());
      }

      AttributesImpl rootAtts = new AttributesImpl(wmsVersion);
      rootAtts.addAttribute(
          "", "updateSequence", "updateSequence", "", wmsConfig.getUpdateSequence() + "");
      start("WMT_MS_Capabilities", rootAtts);
      handleService();
      handleCapability();
      end("WMT_MS_Capabilities");
    }
    private void handleSLD() {
      AttributesImpl sldAtts = new AttributesImpl();

      String supportsSLD = wmsConfig.supportsSLD() ? "1" : "0";
      String supportsUserLayer = wmsConfig.supportsUserLayer() ? "1" : "0";
      String supportsUserStyle = wmsConfig.supportsUserStyle() ? "1" : "0";
      String supportsRemoteWFS = wmsConfig.supportsRemoteWFS() ? "1" : "0";
      sldAtts.addAttribute("", "SupportSLD", "SupportSLD", "", supportsSLD);
      sldAtts.addAttribute("", "UserLayer", "UserLayer", "", supportsUserLayer);
      sldAtts.addAttribute("", "UserStyle", "UserStyle", "", supportsUserStyle);
      sldAtts.addAttribute("", "RemoteWFS", "RemoteWFS", "", supportsRemoteWFS);

      start("UserDefinedSymbolization", sldAtts);
      // djb: this was removed, even though they are correct - the CITE tests have an
      // incorrect DTD
      // element("SupportedSLDVersion","1.0.0"); //djb: added that we support this. We support
      // partial 1.1
      end("UserDefinedSymbolization");

      // element("UserDefinedSymbolization", null, sldAtts);
    }
    /**
     * Turns the metadata URL list to XML
     *
     * @param keywords
     */
    private void handleMetadataList(Collection<MetadataLinkInfo> metadataURLs) {
      if (metadataURLs == null) {
        return;
      }

      for (MetadataLinkInfo link : metadataURLs) {

        AttributesImpl lnkAtts = new AttributesImpl();
        lnkAtts.addAttribute("", "type", "type", "", link.getMetadataType());
        start("MetadataURL", lnkAtts);

        element("Format", link.getType());

        AttributesImpl orAtts = new AttributesImpl();
        orAtts.addAttribute("", "xmlns:xlink", "xmlns:xlink", "", XLINK_NS);
        orAtts.addAttribute(XLINK_NS, "xlink:type", "xlink:type", "", "simple");
        orAtts.addAttribute("", "xlink:href", "xlink:href", "", link.getContent());
        element("OnlineResource", null, orAtts);

        end("MetadataURL");
      }
    }
    /** Encodes the service metadata section of a WMS capabilities document. */
    private void handleService() {
      start("Service");

      final WMSInfo serviceInfo = wmsConfig.getServiceInfo();
      element("Name", "OGC:WMS");
      element("Title", serviceInfo.getTitle());
      element("Abstract", serviceInfo.getAbstract());

      handleKeywordList(serviceInfo.getKeywords());

      AttributesImpl orAtts = new AttributesImpl();
      orAtts.addAttribute("", "xmlns:xlink", "xmlns:xlink", "", XLINK_NS);
      orAtts.addAttribute(XLINK_NS, "xlink:type", "xlink:type", "", "simple");

      String onlineResource = serviceInfo.getOnlineResource();
      if (onlineResource == null || onlineResource.trim().length() == 0) {
        String requestBaseUrl = request.getBaseUrl();
        onlineResource = buildURL(requestBaseUrl, null, null, URLType.SERVICE);
      } else {
        try {
          new URL(onlineResource);
        } catch (MalformedURLException e) {
          LOGGER.log(
              Level.WARNING,
              "WMS online resource seems to be an invalid URL: '" + onlineResource + "'");
        }
      }
      orAtts.addAttribute("", "xlink:href", "xlink:href", "", onlineResource);
      element("OnlineResource", null, orAtts);

      GeoServer geoServer = wmsConfig.getGeoServer();
      ContactInfo contact = geoServer.getGlobal().getContact();
      handleContactInfo(contact);

      element("Fees", serviceInfo.getFees());
      element("AccessConstraints", serviceInfo.getAccessConstraints());
      end("Service");
    }
    /**
     * Writes layer LegendURL pointing to the user supplied icon URL, if any, or to the proper
     * GetLegendGraphic operation if an URL was not supplied by configuration file.
     *
     * <p>It is common practice to supply a URL to a WMS accesible legend graphic when it is
     * difficult to create a dynamic legend for a layer.
     *
     * @param ft The FeatureTypeInfo that holds the legendURL to write out, or<code>null</code> if
     *     dynamically generated.
     * @task TODO: figure out how to unhack legend parameters such as WIDTH, HEIGHT and FORMAT
     */
    protected void handleLegendURL(String layerName, LegendInfo legend, StyleInfo style) {
      if (legend != null) {
        if (LOGGER.isLoggable(Level.FINE)) {
          LOGGER.fine("using user supplied legend URL");
        }

        AttributesImpl attrs = new AttributesImpl();
        attrs.addAttribute("", "width", "width", "", String.valueOf(legend.getWidth()));
        attrs.addAttribute("", "height", "height", "", String.valueOf(legend.getHeight()));

        start("LegendURL", attrs);

        element("Format", legend.getFormat());
        attrs.clear();
        attrs.addAttribute("", "xmlns:xlink", "xmlns:xlink", "", XLINK_NS);
        attrs.addAttribute(XLINK_NS, "type", "xlink:type", "", "simple");
        attrs.addAttribute(XLINK_NS, "href", "xlink:href", "", legend.getOnlineResource());

        element("OnlineResource", null, attrs);

        end("LegendURL");
      } else {
        String defaultFormat = GetLegendGraphicRequest.DEFAULT_FORMAT;

        if (null == wmsConfig.getLegendGraphicOutputFormat(defaultFormat)) {
          if (LOGGER.isLoggable(Level.WARNING)) {
            LOGGER.warning(
                new StringBuffer("Default legend format (")
                    .append(defaultFormat)
                    .append(")is not supported (jai not available?), can't add LegendURL element")
                    .toString());
          }

          return;
        }

        if (LOGGER.isLoggable(Level.FINE)) {
          LOGGER.fine("Adding GetLegendGraphic call as LegendURL");
        }

        AttributesImpl attrs = new AttributesImpl();
        attrs.addAttribute(
            "", "width", "width", "", String.valueOf(GetLegendGraphicRequest.DEFAULT_WIDTH));

        // DJB: problem here is that we do not know the size of the
        // legend apriori - we need
        // to make one and find its height. Not the best way, but it
        // would work quite well.
        // This was advertising a 20*20 icon, but actually producing
        // ones of a different size.
        // An alternative is to just scale the resulting icon to what
        // the server requested, but this isnt
        // the nicest thing since warped images dont look nice. The
        // client should do the warping.

        // however, to actually estimate the size is a bit difficult.
        // I'm going to do the scaling
        // so it obeys the what the request says. For people with a
        // problem with that should consider
        // changing the default size here so that the request is for the
        // correct size.
        attrs.addAttribute(
            "", "height", "height", "", String.valueOf(GetLegendGraphicRequest.DEFAULT_HEIGHT));

        start("LegendURL", attrs);

        element("Format", defaultFormat);
        attrs.clear();

        Map<String, String> params =
            params(
                "request",
                "GetLegendGraphic",
                "format",
                defaultFormat,
                "width",
                String.valueOf(GetLegendGraphicRequest.DEFAULT_WIDTH),
                "height",
                String.valueOf(GetLegendGraphicRequest.DEFAULT_HEIGHT),
                "layer",
                layerName);
        if (style != null) {
          params.put("style", style.getName());
        }
        String legendURL = buildURL(request.getBaseUrl(), "wms", params, URLType.SERVICE);

        attrs.addAttribute("", "xmlns:xlink", "xmlns:xlink", "", XLINK_NS);
        attrs.addAttribute(XLINK_NS, "type", "xlink:type", "", "simple");
        attrs.addAttribute(XLINK_NS, "href", "xlink:href", "", legendURL);
        element("OnlineResource", null, attrs);

        end("LegendURL");
      }
    }
    protected void handleLayerGroups(List<LayerGroupInfo> layerGroups)
        throws FactoryException, TransformException {
      if (layerGroups == null || layerGroups.size() == 0) {
        return;
      }

      Collections.sort(
          layerGroups,
          new Comparator<LayerGroupInfo>() {
            public int compare(LayerGroupInfo o1, LayerGroupInfo o2) {
              return o1.getName().compareTo(o2.getName());
            }
          });

      for (LayerGroupInfo layerGroup : layerGroups) {
        String layerName = layerGroup.getName();

        AttributesImpl qatts = new AttributesImpl();
        boolean queryable = wmsConfig.isQueryable(layerGroup);
        qatts.addAttribute("", "queryable", "queryable", "", queryable ? "1" : "0");
        // qatts.addAttribute("", "opaque", "opaque", "", "1");
        // qatts.addAttribute("", "cascaded", "cascaded", "", "1");
        start("Layer", qatts);
        element("Name", layerName);
        element("Title", layerName);
        element("Abstract", "Layer-Group type layer: " + layerName);

        final ReferencedEnvelope layerGroupBounds = layerGroup.getBounds();
        final ReferencedEnvelope latLonBounds =
            layerGroupBounds.transform(DefaultGeographicCRS.WGS84, true);

        String authority =
            layerGroupBounds
                .getCoordinateReferenceSystem()
                .getIdentifiers()
                .toArray()[0]
                .toString();

        element("SRS", authority);

        handleLatLonBBox(latLonBounds);
        handleBBox(layerGroupBounds, authority);

        // Aggregated metadata links (see GEOS-4500)
        List<LayerInfo> layers = layerGroup.getLayers();
        Set<MetadataLinkInfo> aggregatedLinks = new HashSet<MetadataLinkInfo>();
        for (LayerInfo layer : layers) {
          List<MetadataLinkInfo> metadataLinks = layer.getResource().getMetadataLinks();
          if (metadataLinks != null) {
            aggregatedLinks.addAll(metadataLinks);
          }
        }
        handleMetadataList(aggregatedLinks);

        // the layer style is not provided since the group does just have
        // one possibility, the lack of styles that will make it use
        // the default ones for each layer

        end("Layer");
      }
    }
    /**
     * Calls super.handleFeatureType to add common FeatureType content such as Name, Title and
     * LatLonBoundingBox, and then writes WMS specific layer properties as Styles, Scale Hint, etc.
     *
     * @throws IOException
     * @task TODO: write wms specific elements.
     */
    @SuppressWarnings("deprecation")
    protected void handleLayer(final LayerInfo layer) {
      // HACK: by now all our layers are queryable, since they reference
      // only featuretypes managed by this server
      AttributesImpl qatts = new AttributesImpl();
      boolean queryable = wmsConfig.isQueryable(layer);
      qatts.addAttribute("", "queryable", "queryable", "", queryable ? "1" : "0");
      start("Layer", qatts);
      element("Name", layer.getResource().getNamespace().getPrefix() + ":" + layer.getName());
      // REVISIT: this is bad, layer should have title and anbstract by itself
      element("Title", layer.getResource().getTitle());
      element("Abstract", layer.getResource().getAbstract());
      handleKeywordList(layer.getResource().getKeywords());

      /**
       * @task REVISIT: should getSRS() return the full URL? no - the spec says it should be a set
       *     of <SRS>EPSG:#</SRS>...
       */
      final String srs = layer.getResource().getSRS();
      element("SRS", srs);

      // DJB: I want to be nice to the people reading the capabilities
      // file - I'm going to get the
      // human readable name and stick it in the capabilities file
      // NOTE: this isnt well done because "comment()" isnt in the
      // ContentHandler interface...
      try {
        CoordinateReferenceSystem crs = layer.getResource().getCRS();
        String desc = "WKT definition of this CRS:\n" + crs;
        comment(desc);
      } catch (Exception e) {
        if (LOGGER.isLoggable(Level.WARNING)) {
          LOGGER.log(Level.WARNING, e.getLocalizedMessage(), e);
        }
      }

      Envelope bbox;
      try {
        bbox = layer.getResource().boundingBox();
      } catch (Exception e) {
        throw new RuntimeException(
            "Unexpected error obtaining bounding box for layer " + layer.getName(), e);
      }
      Envelope llbbox = layer.getResource().getLatLonBoundingBox();

      handleLatLonBBox(llbbox);
      // the native bbox might be null
      if (bbox != null) {
        handleBBox(bbox, srs);
      }

      // handle dimensions
      String timeMetadata = null;
      String elevationMetadata = null;

      if (layer.getType() == Type.VECTOR) {
        dimensionHelper.handleVectorLayerDimensions(layer);
      } else if (layer.getType() == Type.RASTER) {
        dimensionHelper.handleRasterLayerDimensions(layer);
      }

      // handle data attribution
      handleAttribution(layer);

      // handle metadata URLs
      handleMetadataList(layer.getResource().getMetadataLinks());

      if (layer.getResource() instanceof WMSLayerInfo) {
        // do nothing for the moment, we may want to list the set of cascaded named styles
        // in the future (when we add support for that)
      } else {
        // add the layer style
        start("Style");

        StyleInfo defaultStyle = layer.getDefaultStyle();
        if (defaultStyle == null) {
          throw new NullPointerException("Layer " + layer.getName() + " has no default style");
        }
        Style ftStyle;
        try {
          ftStyle = defaultStyle.getStyle();
        } catch (IOException e) {
          throw new RuntimeException(e);
        }
        element("Name", defaultStyle.getName());
        element("Title", ftStyle.getTitle());
        element("Abstract", ftStyle.getAbstract());
        handleLegendURL(layer.getName(), layer.getLegend(), null);
        end("Style");

        Set<StyleInfo> styles = layer.getStyles();

        for (StyleInfo styleInfo : styles) {
          try {
            ftStyle = styleInfo.getStyle();
          } catch (IOException e) {
            throw new RuntimeException(e);
          }
          start("Style");
          element("Name", styleInfo.getName());
          element("Title", ftStyle.getTitle());
          element("Abstract", ftStyle.getAbstract());
          handleLegendURL(layer.getName(), null, styleInfo);
          end("Style");
        }
      }

      end("Layer");
    }
    private void handleRequest() {
      start("Request");

      start("GetCapabilities");
      element("Format", WMS_CAPS_MIME);

      // build the service URL and make sure it ends with &
      String serviceUrl =
          buildURL(request.getBaseUrl(), "wms", params("SERVICE", "WMS"), URLType.SERVICE);
      serviceUrl = appendQueryString(serviceUrl, "");

      handleDcpType(serviceUrl, serviceUrl);
      end("GetCapabilities");

      start("GetMap");

      List<String> sortedFormats = new ArrayList<String>(getMapFormats);
      Collections.sort(sortedFormats);
      // this is a hack necessary to make cite tests pass: we need an output format
      // that is equal to the mime type as the first one....
      if (sortedFormats.contains("image/png")) {
        sortedFormats.remove("image/png");
        sortedFormats.add(0, "image/png");
      }
      for (Iterator<String> it = sortedFormats.iterator(); it.hasNext(); ) {
        element("Format", String.valueOf(it.next()));
      }

      handleDcpType(serviceUrl, null);
      end("GetMap");

      start("GetFeatureInfo");

      for (String format : wmsConfig.getAvailableFeatureInfoFormats()) {
        element("Format", format);
      }

      handleDcpType(serviceUrl, serviceUrl);
      end("GetFeatureInfo");

      start("DescribeLayer");
      element("Format", DescribeLayerResponse.DESCLAYER_MIME_TYPE);
      handleDcpType(serviceUrl, null);
      end("DescribeLayer");

      start("GetLegendGraphic");

      for (String format : getLegendGraphicFormats) {
        element("Format", format);
      }

      handleDcpType(serviceUrl, null);
      end("GetLegendGraphic");

      start("GetStyles");
      element("Format", GetStylesResponse.SLD_MIME_TYPE);
      handleDcpType(serviceUrl, null);
      end("GetStyles");

      end("Request");
    }