/* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved.
* This code is licensed under the GPL 2.0 license, availible at the root
* application directory.
*/
package org.vfny.geoserver.wms.responses.helpers;
import static org.geoserver.ows.util.ResponseUtils.appendQueryString;
import static org.geoserver.ows.util.ResponseUtils.buildSchemaURL;
import static org.geoserver.ows.util.ResponseUtils.buildURL;
import static org.geoserver.ows.util.ResponseUtils.params;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import org.apache.xalan.transformer.TransformerIdentityImpl;
import org.geoserver.catalog.AttributionInfo;
import org.geoserver.catalog.FeatureTypeInfo;
import org.geoserver.catalog.LayerGroupInfo;
import org.geoserver.catalog.LayerInfo;
import org.geoserver.catalog.LegendInfo;
import org.geoserver.catalog.MetadataLinkInfo;
import org.geoserver.catalog.ResourceInfo;
import org.geoserver.catalog.StyleInfo;
import org.geoserver.catalog.LayerInfo.Type;
import org.geoserver.config.ContactInfo;
import org.geoserver.config.GeoServer;
import org.geoserver.ows.URLMangler.URLType;
import org.geoserver.wms.WMS;
import org.geoserver.wms.WMSInfo;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.styling.Style;
import org.geotools.xml.transform.TransformerBase;
import org.geotools.xml.transform.Translator;
import org.opengis.feature.type.Name;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.TransformException;
import org.vfny.geoserver.wms.WmsException;
import org.vfny.geoserver.wms.requests.GetLegendGraphicRequest;
import org.vfny.geoserver.wms.requests.WMSCapabilitiesRequest;
import org.vfny.geoserver.wms.responses.DescribeLayerResponse;
import org.vfny.geoserver.wms.responses.GetFeatureInfoResponse;
import org.vfny.geoserver.wms.responses.GetLegendGraphicResponse;
import org.vfny.geoserver.wms.responses.StylesResponse;
import org.xml.sax.ContentHandler;
import org.xml.sax.helpers.AttributesImpl;
import com.vividsolutions.jts.geom.Envelope;
/**
* Geotools xml framework based encoder for a Capabilities WMS 1.1.1 document.
*
* @author Gabriel Roldan, Axios Engineering
* @version $Id
*/
public class WMSCapsTransformer extends TransformerBase {
/** fixed MIME type for the returned capabilities document */
public static final String WMS_CAPS_MIME = "application/vnd.ogc.wms_xml";
/** the WMS supported exception formats */
static final String[] EXCEPTION_FORMATS = { "application/vnd.ogc.se_xml",
"application/vnd.ogc.se_inimage", };
/** The geoserver base URL to append it the schemas/wms/1.1.1/WMS_MS_Capabilities.dtd DTD location */
private String baseURL;
/** The list of output formats to state as supported for the GetMap request */
private Set<String> getMapFormats;
/** The list of output formats to state as supported for the GetLegendGraphic request */
private Set<String> getLegendGraphicFormats;
/**
* Creates a new WMSCapsTransformer object.
*
* @param schemaBaseUrl
* the base URL of the current request (usually "http://host:port/geoserver")
* @param getMapFormats the list of supported output formats to state for the GetMap request
* @param getLegendGraphicFormats the list of supported output formats to state for the
* GetLegendGraphic request
*
* @throws NullPointerException
* if <code>schemaBaseUrl</code> is null;
*/
public WMSCapsTransformer(String baseURL, Set<String> getMapFormats, Set<String> getLegendGraphicFormats) {
super();
if (baseURL == null) {
throw new NullPointerException("baseURL");
}
if (getMapFormats == null) {
throw new NullPointerException("getMapFormats");
}
if (getLegendGraphicFormats == null) {
throw new NullPointerException("getLegendGraphicFormats");
}
this.getMapFormats = getMapFormats;
this.getLegendGraphicFormats = getLegendGraphicFormats;
this.baseURL = baseURL;
this.setNamespaceDeclarationEnabled(false);
}
@Override
public Translator createTranslator(ContentHandler handler) {
return new CapabilitiesTranslator(handler, getMapFormats, getLegendGraphicFormats);
}
/**
* Gets the <code>Transformer</code> created by the overriden method in
* the superclass and adds it the system DOCTYPE token pointing to the
* Capabilities DTD on this server instance.
*
* <p>
* The DTD is set at the fixed location given by the
* <code>schemaBaseUrl</code> passed to the constructor <code>+
* "wms/1.1.1/WMS_MS_Capabilities.dtd</code>.
* </p>
*
* @return a Transformer propoerly configured to produce DescribeLayer
* responses.
*
* @throws TransformerException
* if it is thrown by <code>super.createTransformer()</code>
*/
@Override
public Transformer createTransformer() throws TransformerException {
Transformer transformer = super.createTransformer();
String dtdUrl = buildSchemaURL(baseURL, "wms/1.1.1/WMS_MS_Capabilities.dtd");
transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, dtdUrl);
return transformer;
}
/**
* DOCUMENT ME!
*
* @author Gabriel Roldan, Axios Engineering
* @version $Id
*/
private static class CapabilitiesTranslator extends TranslatorSupport {
private static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger(CapabilitiesTranslator.class.getPackage()
.getName());
private static final String EPSG = "EPSG:";
private static AttributesImpl wmsVersion = new AttributesImpl();
private static final String XLINK_NS = "http://www.w3.org/1999/xlink";
static {
wmsVersion.addAttribute("", "version", "version", "", "1.1.1");
}
/**
* The request from wich all the information needed to produce the
* capabilities document can be obtained
*/
private WMSCapabilitiesRequest request;
private Set<String> getMapFormats;
private Set<String> getLegendGraphicFormats;
private WMS wmsConfig;
/**
* Creates a new CapabilitiesTranslator object.
*
* @param handler
* content handler to send sax events to.
*/
public CapabilitiesTranslator(ContentHandler handler, Set<String> getMapFormats,
Set<String> getLegendGraphicFormats) {
super(handler, null, null);
this.getMapFormats = getMapFormats;
this.getLegendGraphicFormats = getLegendGraphicFormats;
}
/**
* DOCUMENT ME!
*
* @param o the {@link WMSCapabilitiesRequest}
* @throws IllegalArgumentException if {@code o} is not of the expected type
*/
public void encode(Object o) throws IllegalArgumentException {
if (!(o instanceof WMSCapabilitiesRequest)) {
throw new IllegalArgumentException();
}
this.request = (WMSCapabilitiesRequest) o;
this.wmsConfig = this.request.getWMS();
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");
}
/**
* 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");
orAtts.addAttribute("", "xlink:href", "xlink:href", "",
buildURL(request.getBaseUrl(), "wms", null, URLType.SERVICE));
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");
}
/**
* 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");
}
/**
* 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");
}
/**
* Turns the metadata URL list to XML
*
* @param keywords
*/
private void handleMetadataList(List<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 capabilities metadata section of a WMS capabilities
* document
*/
private void handleCapability() {
start("Capability");
handleRequest();
handleException();
handleSLD();
handleLayers();
end("Capability");
}
/**
* DOCUMENT ME!
*/
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 (Iterator it = GetFeatureInfoResponse.getFormats().iterator(); it.hasNext();) {
element("Format", String.valueOf(it.next()));
}
handleDcpType(serviceUrl, serviceUrl);
end("GetFeatureInfo");
start("DescribeLayer");
element("Format", DescribeLayerResponse.DESCLAYER_MIME_TYPE);
handleDcpType(serviceUrl, null);
end("DescribeLayer");
start("GetLegendGraphic");
for (Iterator it = getLegendGraphicFormats.iterator();
it.hasNext();) {
element("Format", String.valueOf(it.next()));
}
handleDcpType(serviceUrl, null);
end("GetLegendGraphic");
start("GetStyles");
element("Format", StylesResponse.SLD_MIME_TYPE);
handleDcpType(serviceUrl, null);
end("GetStyles");
end("Request");
}
/**
* 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");
}
/**
* DOCUMENT ME!
*/
private void handleException() {
start("Exception");
for(String exceptionFormat : WMSCapsTransformer.EXCEPTION_FORMATS) {
element("Format", exceptionFormat);
}
end("Exception");
}
/**
* DOCUMENT ME!
*/
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);
}
/**
* 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 <SRS>
* elements listing all SRSes that are common to all subsidiary layers.
* Use a single SRS element with empty content (like so:
* "<SRS></SRS>") if there is no common SRS."</i>
* </p>
*
* <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>
* </p>
*
* @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 = srsList == null? Collections.EMPTY_SET : new HashSet<String>(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");
}
/**
* Called by <code>handleLayers()</code>, writes down list of
* supported CRS's for the root Layer.
* <p>
* If <code>epsgCodes</code> is not empty, the list of supported CRS
* identifiers written down to the capabilities document is limited to
* those in the <code>epsgCodes</code> list. Otherwise, all the
* GeoServer supported CRS identifiers are used.
* </p>
*
* @param epsgCodes
* possibly empty set of CRS identifiers to limit the number
* of SRS elements to.
*/
private void handleRootCrsList(final Set<String> epsgCodes) {
final Set<String> capabilitiesCrsIdentifiers;
if(epsgCodes.isEmpty()){
comment("All supported EPSG projections:");
capabilitiesCrsIdentifiers = new LinkedHashSet<String>();
for(String code: CRS.getSupportedCodes("AUTO")) {
if("WGS84(DD)".equals(code))
continue;
capabilitiesCrsIdentifiers.add("AUTO:" + code);
}
capabilitiesCrsIdentifiers.addAll(CRS.getSupportedCodes("EPSG"));
} else {
comment("Limited list of EPSG projections:");
capabilitiesCrsIdentifiers = new TreeSet<String>(epsgCodes);
}
try {
Iterator<String> it = capabilitiesCrsIdentifiers.iterator();
String currentSRS;
while (it.hasNext()) {
currentSRS = it.next();
if(currentSRS.indexOf(':') == -1){
currentSRS = EPSG + currentSRS;
}
element("SRS", currentSRS);
}
} catch (Exception e) {
LOGGER.log(Level.WARNING, e.getLocalizedMessage(), e);
}
}
/**
* Called by <code>handleLayers()</code>, iterates over the available
* featuretypes and coverages to summarize their LatLonBBox'es and write
* the aggregated bounds for the root layer.
*
* @param ftypes
* the collection of FeatureTypeInfo and CoverageInfo objects
* to traverse
*/
private void handleRootBbox(Collection<LayerInfo> layers) {
Envelope latlonBbox = new Envelope();
Envelope layerBbox = null;
LOGGER.finer("Collecting summarized latlonbbox and common SRS...");
for (LayerInfo layer : layers) {
ResourceInfo resource = layer.getResource();
layerBbox = resource.getLatLonBoundingBox();
latlonBbox.expandToInclude(layerBbox);
}
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("Summarized LatLonBBox is " + latlonBbox);
}
handleLatLonBBox(latlonBbox);
}
/**
* @param layerTree
*/
private void handleLayerTree(final LayerTree layerTree) {
final List<LayerInfo> data = new ArrayList<LayerInfo>(layerTree.getData());
final Collection<LayerTree> childrens = 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) {
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 WmsException("Error occurred trying to write out metadata for layer: " + layer.getName(), "", e);
}
}
}
LayerTree childLayerTree;
for (Iterator it = childrens.iterator(); it.hasNext();) {
childLayerTree = (LayerTree) it.next();
start("Layer");
element("Name", childLayerTree.getName());
element("Title", childLayerTree.getName());
handleLayerTree(childLayerTree);
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.
*/
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();
qatts.addAttribute("", "queryable", "queryable", "", "1");
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 data attribution
handleAttribution(layer);
// handle metadata URLs
handleMetadataList(layer.getResource().getMetadataLinks());
// 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");
}
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());
}
});
CoordinateReferenceSystem wgs84 = CRS.decode("EPSG:4326");
for (LayerGroupInfo layerGroup : layerGroups) {
String layerName = layerGroup.getName();
AttributesImpl qatts = new AttributesImpl();
qatts.addAttribute("", "queryable", "queryable", "", "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);
// 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");
}
}
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");
}
}
/**
* 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.
* </p>
*
* @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 (!GetLegendGraphicResponse.supportsFormat(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");
}
}
/**
* Encodes a LatLonBoundingBox for the given Envelope.
*
* @param bbox
*/
private void handleLatLonBBox(Envelope bbox) {
String minx = String.valueOf(bbox.getMinX());
String miny = String.valueOf(bbox.getMinY());
String maxx = String.valueOf(bbox.getMaxX());
String maxy = String.valueOf(bbox.getMaxY());
AttributesImpl bboxAtts = new AttributesImpl();
bboxAtts.addAttribute("", "minx", "minx", "", minx);
bboxAtts.addAttribute("", "miny", "miny", "", miny);
bboxAtts.addAttribute("", "maxx", "maxx", "", maxx);
bboxAtts.addAttribute("", "maxy", "maxy", "", maxy);
element("LatLonBoundingBox", null, bboxAtts);
}
/**
* adds a comment to the output xml file. THIS IS A BIG HACK. TODO: do
* this in the correct manner!
*
* @param comment
*/
public void comment(String comment) {
if (contentHandler instanceof TransformerIdentityImpl) // HACK HACK
// HACK --
// not sure
// of the
// proper
// way to do
// this.
{
try {
TransformerIdentityImpl ch = (TransformerIdentityImpl) contentHandler;
ch.comment(comment.toCharArray(), 0, comment.length());
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* Encodes a BoundingBox for the given Envelope.
*
* @param bbox
*/
private void handleBBox(Envelope bbox, String SRS) {
String minx = String.valueOf(bbox.getMinX());
String miny = String.valueOf(bbox.getMinY());
String maxx = String.valueOf(bbox.getMaxX());
String maxy = String.valueOf(bbox.getMaxY());
AttributesImpl bboxAtts = new AttributesImpl();
bboxAtts.addAttribute("", "SRS", "SRS", "", SRS);
bboxAtts.addAttribute("", "minx", "minx", "", minx);
bboxAtts.addAttribute("", "miny", "miny", "", miny);
bboxAtts.addAttribute("", "maxx", "maxx", "", maxx);
bboxAtts.addAttribute("", "maxy", "maxy", "", maxy);
element("BoundingBox", null, bboxAtts);
}
}
}
/**
* A Class to manage the WMS Layer structure
*
* @author fabiania
*
* TODO To change the template for this generated type comment go to Window -
* Preferences - Java - Code Style - Code Templates
*/
class LayerTree {
private String name;
private Collection<LayerTree> childrens;
private Collection<LayerInfo> data;
/**
* @param name
* String
*/
public LayerTree(String name) {
this.name = name;
this.childrens = new ArrayList<LayerTree>();
this.data = new ArrayList<LayerInfo>();
}
/**
* @param c
* Collection
*/
public LayerTree(Collection<LayerInfo> c) {
this.name = "";
this.childrens = new ArrayList();
this.data = new ArrayList();
for (Iterator<LayerInfo> it = c.iterator(); it.hasNext();) {
LayerInfo layer = it.next();
// ask for enabled() instead of isEnabled() to account for disabled resource/store
if (layer.enabled()) {
String wmsPath = layer.getPath() == null? "" : layer.getPath();
if (wmsPath.startsWith("/")) {
wmsPath = wmsPath.substring(1, wmsPath.length());
}
String[] treeStructure = wmsPath.split("/");
addToNode(this, treeStructure, layer);
}
}
}
/**
* @param tree
* @param treeStructure
* @param layer
*/
private void addToNode(LayerTree tree, String[] treeStructure, LayerInfo layer) {
final int length = treeStructure.length;
if ((length == 0) || (treeStructure[0].length() == 0)) {
tree.data.add(layer);
} else {
LayerTree node = tree.getNode(treeStructure[0]);
if (node == null) {
node = new LayerTree(treeStructure[0]);
tree.childrens.add(node);
}
String[] subTreeStructure = new String[length - 1];
System.arraycopy(treeStructure, 1, subTreeStructure, 0, length - 1);
addToNode(node, subTreeStructure, layer);
}
}
/**
* @param string
* @return
*/
public LayerTree getNode(String name) {
LayerTree node = null;
for (Iterator it = this.childrens.iterator(); it.hasNext();) {
LayerTree tmpNode = (LayerTree) it.next();
if (tmpNode.name.equals(name)) {
node = tmpNode;
}
}
return node;
}
public Collection<LayerTree> getChildrens() {
return childrens;
}
public Collection<LayerInfo> getData() {
return data;
}
public String getName() {
return name;
}
}