PageRenderTime 81ms CodeModel.GetById 37ms app.highlight 36ms RepoModel.GetById 1ms app.codeStats 0ms

/webportal/src/main/java/au/org/emii/portal/util/LayerUtilitiesImpl.java

http://alageospatialportal.googlecode.com/
Java | 1141 lines | 647 code | 134 blank | 360 comment | 73 complexity | 76945df1c2d0be757c9f71a70d2bc399 MD5 | raw file
   1package au.org.emii.portal.util;
   2
   3import au.org.emii.portal.menu.MapLayer;
   4import au.org.emii.portal.net.HttpConnection;
   5import au.org.emii.portal.settings.Settings;
   6import au.org.emii.portal.settings.SettingsSupplementary;
   7import java.io.UnsupportedEncodingException;
   8import java.net.URLDecoder;
   9import java.net.URLEncoder;
  10import java.text.BreakIterator;
  11import java.text.DateFormat;
  12import java.util.ArrayList;
  13import java.util.Date;
  14import java.util.List;
  15import java.util.regex.Matcher;
  16import java.util.regex.Pattern;
  17import org.apache.commons.lang.StringEscapeUtils;
  18import java.io.IOException;
  19import javax.xml.parsers.DocumentBuilder;
  20import javax.xml.parsers.DocumentBuilderFactory;
  21import javax.xml.parsers.ParserConfigurationException;
  22import org.apache.log4j.Logger;
  23import org.springframework.beans.factory.annotation.Required;
  24import net.sf.json.JSONArray;
  25import net.sf.json.JSONObject;
  26import org.ala.spatial.util.CommonData;
  27import org.apache.commons.httpclient.HttpClient;
  28import org.apache.commons.httpclient.methods.GetMethod;
  29import org.apache.log4j.Priority;
  30import org.w3c.dom.Document;
  31import org.w3c.dom.NodeList;
  32import org.xml.sax.SAXException;
  33
  34/**
  35 * WMS and ncWMS/THREDDS url manipulation class
  36 *
  37 * Supported types (string representation):
  38 *
  39 *      "WMS-1.0.0"
  40 *      "WMS-LAYER-1.0.0"               
  41 *      "WMS-1.1.0"
  42 *      "WMS-LAYER-1.1.0"
  43 *      "WMS-1.1.1"
  44 *      "WMS-LAYER-1.1.1"
  45 *      "WMS-1.3.0"
  46 *      "WMS-LAYER-1.3.0"
  47 *      "NCWMS"
  48 *      "THREDDS"
  49 *      "GEORSS"
  50 *      "KML"
  51 *      "WKT"
  52 *      "AUTO"  NOTE: Auto discover WMS server - only has meaning during discovery process
  53 * @author geoff
  54 *
  55 */
  56public class LayerUtilitiesImpl implements LayerUtilities {
  57
  58    private Logger logger = Logger.getLogger(getClass());
  59    private final static String GEOSERVER_REGEXP = "[Gg][Ee][Oo][Ss][Ee][Rr][Vv][Ee][Rr]";
  60    private final static String NCWMS_REGEXP = "[Nn][Cc][Ww][Mm][Ss]";
  61    private final static String IMAGE_FORMAT_REGEXP = "[Ff][Oo][Rr][Mm][Aa][Tt]";
  62    private final static String LAYERS_REGEXP = "[Ll][Aa][Yy][Ee][Rr][Ss]";
  63    private final static String LAYER_REGEXP = "[Ll][Aa][Yy][Ee][Rr]";
  64    private final static String VERSION_REGEXP = "[Vv][Ee][Rr][Ss][Ii][Oo][Nn]";
  65    private ArrayList<String> versions = null;
  66    private Settings settings = null;
  67    private SettingsSupplementary settingsSupplementary = null;
  68    private ResolveHostName resolveHostname = null;
  69    private List<Double> worldBBox = null;
  70
  71    public LayerUtilitiesImpl() {
  72        versions = new ArrayList<String>();
  73        versions.add(WMS_1_0_0, "1.0.0");
  74        versions.add(WMS_1_1_0, "1.1.0");
  75        versions.add(WMS_1_1_1, "1.1.1");
  76        versions.add(WMS_1_3_0, "1.3.0");
  77
  78        versions.add(NCWMS, "NCWMS");
  79        versions.add(THREDDS, "THREDDS");
  80        versions.add(GEORSS, "GEORSS");
  81        versions.add(KML, "KML");
  82        versions.add(GEOJSON, "GEOJSON");
  83
  84        worldBBox = new ArrayList<Double>(4);
  85//        worldBBox.add(-180.0);
  86//        worldBBox.add(-90.0);
  87//        worldBBox.add(180.0);
  88//        worldBBox.add(90.0);
  89        worldBBox.add(-179.999);
  90        worldBBox.add(-89.999);
  91        worldBBox.add(179.999);
  92        worldBBox.add(89.999);
  93    }
  94
  95    /**
  96     * Check whether the passed in type is compatible with any
  97     * WMS version.
  98     *
  99     * NcWMS and THREDDS are considered to be WMS compatible
 100     * @param type
 101     * @return
 102     */
 103    @Override
 104    public boolean supportsWms(int type) {
 105        return ((type == LayerUtilitiesImpl.WMS_1_0_0)
 106                || (type == LayerUtilitiesImpl.WMS_1_1_0)
 107                || (type == LayerUtilitiesImpl.WMS_1_1_1)
 108                || (type == LayerUtilitiesImpl.WMS_1_3_0)
 109                || (type == LayerUtilitiesImpl.NCWMS)
 110                || (type == LayerUtilitiesImpl.THREDDS));
 111    }
 112
 113    /**
 114     * only NCWMS and THREDDS support metadata
 115     * @param type
 116     * @return
 117     */
 118    @Override
 119    public boolean supportsMetadata(int type) {
 120        return ((type == LayerUtilitiesImpl.NCWMS)
 121                || (type == LayerUtilitiesImpl.THREDDS));
 122    }
 123
 124    /**
 125     * only NCWMS and THREDDS support animation
 126     * @param type
 127     * @return
 128     */
 129    @Override
 130    public boolean supportsAnimation(int type) {
 131        return ((type == LayerUtilitiesImpl.NCWMS)
 132                || (type == LayerUtilitiesImpl.THREDDS));
 133    }
 134
 135    /**
 136     * Convert a string WMS version eg (1.3.0) to its integer
 137     * representation within the portal eg WMS_1_3_0
 138     * @param requestedType
 139     * @return Integer constant representing string representation
 140     * of WMS version.  Returns UNSUPPORTED if nothing matches
 141     */
 142    @Override
 143    public int internalVersion(String requestedType) {
 144        int version = UNSUPPORTED;
 145        if (requestedType != null) {
 146            // strip WMS- or WMS-LAYER- from version number
 147            String realVersion =
 148                    requestedType.replaceAll(
 149                    "[Ww][Mm][Ss]-([Ll][Aa][Yy][Ee][Rr]-)?", "");
 150
 151            // Get the version (integer constant) - handily
 152            // returns -1 (unsupported) if no match
 153            version = versions.indexOf(realVersion);
 154        }
 155        return version;
 156    }
 157
 158    @Override
 159    public String getNcWMSTimeStringsUri(String uri, String layer, String startDate, String endDate) {
 160        return uri
 161                + queryConjunction(uri)
 162                + "item=animationTimesteps"
 163                + "&layerName=" + layer
 164                + "&start=" + startDate
 165                + "&end=" + endDate
 166                + "&request=GetMetadata";
 167    }
 168
 169    /**
 170     * Convert the internal integer constant representation of the
 171     * WMS version to an external string as used in the version=
 172     * URI parameter.  This is not the same as the version string
 173     * used in the config file.
 174     *
 175     * NCWMS and THREDDS are special cases and will return "1.3.0"
 176     * @param version
 177     * @return
 178     */
 179    @Override
 180    public String externalVersion(int version) {
 181        String externalVersion = null;
 182        if (version != UNSUPPORTED) {
 183            if (version == NCWMS || version == THREDDS) {
 184                // force ncwms to be v 1.3.0
 185                externalVersion = versions.get(WMS_1_3_0);
 186            } else {
 187                externalVersion = versions.get(version);
 188            }
 189        }
 190        return externalVersion;
 191    }
 192
 193    /**
 194     * Remove Trailing garbage after lookbehind string
 195     *
 196     * http://www.foo.com/geoserver_special/wrongpage.do =>
 197     * 	http://www.foo.com/geoserver_special/
 198     *
 199     * http://www.bar.com/my_ncwms/data/wrongpage.html =>
 200     * 	http://www.bar.com/my_ncwms/
 201     */
 202    private String removeAfterSlash(String uri, String lookBehind) {
 203        return uri.replaceAll(
 204                "(" + lookBehind + "[^/]*/).*$?",
 205                "$1");
 206    }
 207
 208    @Override
 209    public String getVersionValue(String uri) {
 210        return getParameterValue(VERSION_REGEXP, uri);
 211    }
 212
 213    /**
 214     * Construct and return a URI for a 2x2 px test image
 215     * from a GetMap uri.  This can be used to test individual
 216     * wms layers are at least returning images when they are
 217     * added to the map by the user - we don't do this for
 218     * our own layers as we assume that they are working.
 219     * @param uri
 220     * @return
 221     */
 222    @Override
 223    public String getTestImageUri(String uri) {
 224
 225        // step 1 - strip request param, bbox, width and height
 226        String testUri =
 227                stripUriRequest(stripUriBbox(stripUriWidth(stripUriHeight(uri))));
 228
 229
 230        /* LAYERS, CRS/SRS, VERSION and FORMAT should already
 231         * be set
 232         */
 233        testUri +=
 234                queryConjunction(uri)
 235                + "REQUEST=GetMap"
 236                + "&BBOX=1,1,2,2"
 237                + "&WIDTH=2"
 238                + "&HEIGHT=2";
 239
 240        return testUri;
 241    }
 242
 243    /**
 244     * Application specific url tweaking - works for geoserver and
 245     * ncwms at the moment
 246     *
 247     * Autodetect and tweak uri if we detect the presence of geoserver
 248     * or ncwms
 249     * @param uri original uri
 250     * @return mangled uri or null if geoserver or ncwms are not found or if
 251     * mangleing the uri would return the same value
 252     */
 253    @Override
 254    public String mangleUriApplication(String uri) {
 255        String mangled;
 256        String matchGeoserver = ".*?" + GEOSERVER_REGEXP + ".*?";
 257        String matchNcwms = ".*?" + NCWMS_REGEXP + ".*?";
 258        if ((uri.matches(matchGeoserver)) || (uri.matches(matchNcwms))) {
 259            if (uri.matches(matchGeoserver)) {
 260                // geoserver detected
 261                mangled = removeAfterSlash(uri, GEOSERVER_REGEXP);
 262            } else {
 263                mangled = removeAfterSlash(uri, NCWMS_REGEXP);
 264            }
 265            mangled += "/wms";
 266
 267            // final check - only return the mangled uri if its different to the
 268            // one we started with - so that consumers can detect whether to bother
 269            // with further processing by testing for a null value
 270            if (mangled.equals(uri)) {
 271                mangled = null;
 272            }
 273        } else {
 274            mangled = null;
 275        }
 276
 277        return mangled;
 278    }
 279
 280    /**
 281     * Attempt to automatically construct a get capabilities uri for
 282     * the passed in WMS version
 283     * @param uri
 284     * @return
 285     */
 286    @Override
 287    public String mangleUriGetCapabilitiesAutoDiscover(String uri, int version) {
 288        LayerUtilitiesImpl wmsUtilities = new LayerUtilitiesImpl();
 289
 290        // strip any existing version,request and service params
 291        String mangled = stripUriRequest(uri);
 292        mangled = stripUriService(mangled);
 293        mangled = stripUriVersion(mangled);
 294
 295        /* if last char is a '?' or '&' we can append our query
 296         * directly, otherwise we need to append one ourself
 297         */
 298        mangled += queryConjunction(uri);
 299
 300        // replace with our own params
 301        mangled +=
 302                "SERVICE=WMS&"
 303                + "REQUEST=GetCapabilities&"
 304                + "VERSION=" + wmsUtilities.externalVersion(version);
 305
 306        return mangled;
 307    }
 308
 309    /**
 310     * Return either "", "&" or "?" as a conjunction to be
 311     * used when generating urls based on the last character
 312     * in the passed in uri according to the following rules:
 313     *
 314     * 1) 	uri ends with '?' or '&' - uri is ready to use,  return ""
 315     * 2) 	uri doesn't end with '?' or '&' and contains and '?'
 316     * 		somewhere within the string - return '&'
 317     * 3)	uri doesn't end with '?' or '&' and doesn't contain '&'
 318     * 		anywhere within the string - return '?'
 319     * @param uri
 320     * @return
 321     */
 322    @Override
 323    public String queryConjunction(String uri) {
 324        String conjunction = "";
 325        char last = uri.charAt(uri.length() - 1);
 326        if ((last != '?') && (last != '&')) {
 327            if (uri.contains("?")) {
 328                conjunction += "&";
 329            } else {
 330                conjunction += "?";
 331            }
 332        }
 333        return conjunction;
 334    }
 335
 336    /**
 337     * Strip any version information from the URI and use
 338     * the passed in version string instead
 339     * @param uri
 340     * @param version
 341     * @return
 342     */
 343    @Override
 344    public String fixVersion(String uri, String version) {
 345        String fixedUri = stripUriVersion(uri);
 346        fixedUri += queryConjunction(fixedUri);
 347        fixedUri += "&VERSION=" + version;
 348        return fixedUri;
 349    }
 350
 351    @Override
 352    public String stripParameter(String parameter, String uri) {
 353        return uri.replaceAll(parameter + "=([^&]?)*&?", "");
 354    }
 355
 356    @Override
 357    public String stripUriVersion(String uri) {
 358        return stripParameter("[Vv][Ee][Rr][Ss][Ii][Oo][Nn]", uri);
 359    }
 360
 361    @Override
 362    public String stripUriService(String uri) {
 363        return stripParameter("[Ss][Ee][Rr][Vv][Ii][Cc][Ee]", uri);
 364    }
 365
 366    @Override
 367    public String stripUriRequest(String uri) {
 368        return stripParameter("[Rr][Ee][Qq][Uu][Ee][Ss][Tt]", uri);
 369    }
 370
 371    @Override
 372    public String stripUriBbox(String uri) {
 373        return stripParameter("[Bb][Bb][Oo][Xx]", uri);
 374    }
 375
 376    @Override
 377    public String stripUriWidth(String uri) {
 378        return stripParameter("[Ww][Ii][Dd][Tt][Hh]", uri);
 379    }
 380
 381    @Override
 382    public String stripUriHeight(String uri) {
 383        return stripParameter("[Hh][Ee][Ii][Gg][Hh][Tt]", uri);
 384    }
 385
 386    @Override
 387    public int getMaxNameLength() {
 388        int maxLength;
 389        try {
 390            maxLength = Integer.parseInt(settingsSupplementary.getValue("layer_name_max_length"));
 391        } catch (NumberFormatException e) {
 392            maxLength = 20;
 393            logger.error(
 394                    "unable to parse an integer from layer_name_max_length key "
 395                    + "in config file - your configuration is broken.  Will attempt "
 396                    + "to continue using a sensible default value of: " + maxLength);
 397        }
 398        return maxLength;
 399    }
 400
 401    @Override
 402    public boolean needsChomp(String originalName) {
 403        boolean needsChomp;
 404        if (originalName.length() > getMaxNameLength()) {
 405            needsChomp = true;
 406        } else {
 407            needsChomp = false;
 408        }
 409        return needsChomp;
 410    }
 411
 412    @Override
 413    public String getTooltip(String name, String description) {
 414        String tooltip;
 415        tooltip = description;
 416
 417        if (needsChomp(name)) {
 418            // show full name: description as tooltip for names we had to
 419            // chomp
 420            if (!name.equals(description)) {
 421                tooltip = "(" + name + ")" + "  " + description;
 422            }
 423        }
 424        return tooltip;
 425    }
 426
 427    /**
 428     * Chomp the layer name if we need to
 429     * @param layerName
 430     * @return The original name if under maxLength or a chomped name
 431     * if over
 432     */
 433    @Override
 434    public String chompLayerName(String layerName) {
 435        //int length = layerName.length();
 436
 437        layerName = layerName.replace('_', ' ');
 438
 439        /* Chomp very long names, eg :
 440         * "mystupildylongandbrokennamethatscompletelyunreadable_v1"
 441         * "mystupildylongandbrokennamethatscompletelyunreadable_v2"
 442         *
 443         * become:
 444         * "mystupildylongandbrokennametha...v1"
 445         * "mystupildylongandbrokennametha...v2"
 446         */
 447
 448        // limit it gracefully if possible
 449        if (needsChomp(layerName)) {
 450
 451            BreakIterator bi = BreakIterator.getWordInstance();
 452            bi.setText(layerName);
 453            int first_after = bi.following(getMaxNameLength() - 10);
 454            layerName = layerName.substring(0, first_after);
 455
 456            // forced limiting
 457            if (needsChomp(layerName)) {
 458                layerName =
 459                        layerName.substring(0, getMaxNameLength() - 1);
 460            }
 461            layerName = layerName + "...";
 462        }
 463
 464        return layerName;
 465    }
 466
 467    /**
 468     * Parse the image format from a uri
 469     * @param uri
 470     */
 471    @Override
 472    public String getImageFormat(String uri) {
 473        return getParameterValue(IMAGE_FORMAT_REGEXP, uri);
 474    }
 475
 476    /**
 477     * Parse the requested layers from a uri
 478     * @param uri
 479     */
 480    @Override
 481    public String getLayers(String uri) {
 482        return getParameterValue(LAYERS_REGEXP, uri);
 483    }
 484
 485    @Override
 486    public String getLayer(String uri) {
 487        return getParameterValue(LAYER_REGEXP, uri);
 488    }
 489
 490    @Override
 491    public String getParameterValue(String parameter, String uri) {
 492        String value = null;
 493
 494        // parameter value will be held in $1
 495        String fullRegexp = ".*?" + parameter + "=([^&]*)&?.*";
 496        Pattern pattern = Pattern.compile(fullRegexp);
 497        Matcher matcher = pattern.matcher(uri);
 498        while (matcher.find()) {
 499            // got a match
 500            value = matcher.group(1);
 501        }
 502        // base20 decode it as well
 503        if (value != null) {
 504            try {
 505                value = URLDecoder.decode(value, "UTF-8");
 506            } catch (UnsupportedEncodingException e) {
 507                logger.error("missing URL encoder! ", e);
 508            }
 509        }
 510        return value;
 511    }
 512
 513    @Override
 514    public String getLegendGraphicUri(String uri, String imageFormat) {
 515        // kill request=getmap
 516        String legendUri = stripParameter("[Rr][Ee][Qq][Uu][Ee][Ss][Tt]", uri);
 517
 518        legendUri +=
 519                queryConjunction(uri)
 520                + "LEGEND_OPTIONS=forceLabels:on"
 521                + "&REQUEST=GetLegendGraphic"
 522                + "&FORMAT=" + imageFormat;
 523
 524        // layer parameter must always be set - guess it from LAYERS
 525        // if it's currently empty
 526        if (getLayer(uri) == null) {
 527            legendUri += "&LAYER=" + getLayers(uri);
 528        }
 529
 530        return legendUri;
 531    }
 532
 533    @Override
 534    public String getLegendGraphicUri(String uri, String layer, String imageFormat) {
 535        return getLegendGraphicUri(
 536                uri + queryConjunction(uri) + "LAYER=" + layer,
 537                imageFormat);
 538    }
 539
 540    @Override
 541    public String getLegendGraphicUri(String uri, String layer, String imageFormat, String envParams) {
 542
 543        //get the template and add the envParams into it
 544
 545        return getLegendGraphicUri(
 546                uri + queryConjunction(uri) + "LAYER=" + layer,
 547                imageFormat);
 548    }
 549
 550    /**
 551     * Construct a metadata uri.  For use with ncwms/thredds
 552     * @param uri
 553     * @param layer
 554     * @return
 555     */
 556    @Override
 557    public String getMetadataUri(String uri, String layer) {
 558        return uri
 559                + queryConjunction(uri)
 560                + "item=layerDetails"
 561                + "&layerName=" + layer
 562                + "&request=GetMetadata";
 563    }
 564
 565    @Override
 566    public String getTimestepsUri(String uri, String layer, Date date) {
 567        DateFormat df = Validate.getIsoDateFormatter();
 568        return uri
 569                + queryConjunction(uri)
 570                + "item=timesteps"
 571                + "&layerName=" + layer
 572                + "&request=GetMetadata"
 573                + "&day=" + df.format(date);
 574    }
 575
 576    /**
 577     * Generate the URI to request an animated image.
 578     *
 579     * Doesn't currently support:
 580     * 	elevation
 581     * 	colorscalerange
 582     * 	numcolorbands
 583     * 	logscale
 584     * But probably will in the future
 585     * @param mapLayer
 586     * @return
 587     */
 588    private String getAnimationBaseUri(MapLayer mapLayer) {
 589
 590        String baseUri =
 591                getFQUri(mapLayer.getUri())
 592                + queryConjunction(mapLayer.getUri())
 593                + "TRANSPARENT=true"
 594                + //			"&ELEVATION=" + mapLayer.getAnimationParameters().getElevation() +
 595                ((mapLayer.getSelectedStyleName().equals("Default")) ? "" : "&STYLES=" + mapLayer.getSelectedStyleName())
 596                + "&CRS=EPSG%3A4326"
 597                + //			"&COLORSCALERANGE=9.405405%2C29.66159" +
 598                //			"&NUMCOLORBANDS=254" +
 599                //			"&LOGSCALE=false" +
 600                "&SERVICE=WMS"
 601                + "&VERSION=" + getWmsVersion(mapLayer)
 602                + "&EXCEPTIONS=XML"
 603                + "&FORMAT=image/gif";
 604
 605        return baseUri;
 606    }
 607
 608    /**
 609     * Get the fully qualified URI (if it isn't already...)
 610     * - this is to add a hostname to indirect cache requests
 611     * which normally store the URI as /RemoteRequest?url=foo
 612     */
 613    @Override
 614    public String getFQUri(String uri) {
 615        String fqUri;
 616        if (!uri.startsWith("http://")) {
 617            fqUri = "http://" + resolveHostname.resolveHostName();
 618
 619            // be tolerant of missing/extra slashes in the uri
 620            if (!uri.startsWith("/")) {
 621                fqUri += "/";
 622            }
 623            fqUri += uri;
 624        } else {
 625            fqUri = uri;
 626        }
 627        return fqUri;
 628    }
 629
 630    @Override
 631    public String getAnimationUriJS(MapLayer mapLayer) {
 632        return StringEscapeUtils.escapeJavaScript(getAnimationUri(mapLayer));
 633    }
 634
 635    /**
 636     * safe to assume by now that we have accumulated enough
 637     * parameters to need & to join our query...
 638     *
 639     * Width and heights are numbers in the config file - no
 640     * need to parse them to int thought because they're going
 641     * to be used in a string straight away
 642     */
 643    @Override
 644    public String getAnimationUri(MapLayer mapLayer) {
 645
 646
 647        return getAnimationBaseUri(mapLayer)
 648                + "&TIME=" + mapLayer.getAnimationSelection().getSelectedTimeString()
 649                + "&LAYERS=" + mapLayer.getLayer()
 650                + "&REQUEST=GetMap"
 651                + "&WIDTH=" + settingsSupplementary.getValue("animation_width")
 652                + "&HEIGHT=" + settingsSupplementary.getValue("animation_height")
 653                + "&BBOX=" + mapLayer.getMapLayerMetadata().getBboxString();
 654    }
 655
 656    @Override
 657    public String getAnimationFeatureInfoUriJS(MapLayer mapLayer) {
 658        return StringEscapeUtils.escapeJavaScript(getAnimationFeatureInfoUri(mapLayer));
 659    }
 660
 661    @Override
 662    public String getAnimationFeatureInfoUri(MapLayer mapLayer) {
 663        return getAnimationBaseUri(mapLayer)
 664                + "&TIME="
 665                + mapLayer.getAnimationSelection().getAdjustedStartDate() + "/"
 666                + mapLayer.getAnimationSelection().getAdjustedEndDate()
 667                + "&REQUEST=GetFeatureInfo"
 668                + "&QUERY_LAYERS=" + mapLayer.getLayer();
 669    }
 670
 671    @Override
 672    public String getAnimationTimeSeriesPlotUriJS(MapLayer mapLayer) {
 673        return StringEscapeUtils.escapeJavaScript(getAnimationTimeSeriesPlotUri(mapLayer));
 674    }
 675
 676    @Override
 677    public String getAnimationTimeSeriesPlotUri(MapLayer mapLayer) {
 678        return getAnimationFeatureInfoUri(mapLayer)
 679                + "&INFO_FORMAT=image/png";
 680    }
 681
 682    /** indirect caching requested - base url is to be requested
 683     * through our RemoteRequest servlet so squid can cache it
 684     * @throws UnsupportedEncodingException
 685     */
 686    @Override
 687    public String getIndirectCacheUrl(String baseUri) {
 688        String cacheUri =
 689                settings.getCacheUrl()
 690                + "?"
 691                + settings.getCacheParameter()
 692                + "=";
 693        try {
 694            cacheUri += URLEncoder.encode(baseUri, "utf-8");
 695        } catch (UnsupportedEncodingException e) {
 696            // should never happen unless your jdk is FUBAR
 697            logger.error("missing URL encoder! ", e);
 698
 699            // will have to include the naked baseuri and hope for the best
 700            cacheUri += baseUri;
 701        }
 702        return cacheUri;
 703
 704    }
 705
 706    @Override
 707    public int getWms100() {
 708        return WMS_1_0_0;
 709    }
 710
 711    @Override
 712    public int getWms110() {
 713        return WMS_1_1_0;
 714    }
 715
 716    @Override
 717    public int getWms111() {
 718        return WMS_1_1_1;
 719    }
 720
 721    @Override
 722    public int getWms130() {
 723        return WMS_1_3_0;
 724    }
 725
 726    @Override
 727    public int getNcwms() {
 728        return NCWMS;
 729    }
 730
 731    @Override
 732    public int getThredds() {
 733        return THREDDS;
 734    }
 735
 736    @Override
 737    public int getGeorss() {
 738        return GEORSS;
 739    }
 740
 741    @Override
 742    public int getKml() {
 743        return KML;
 744    }
 745
 746    @Override
 747    public int getGeojson() {
 748        return GEOJSON;
 749    }
 750
 751    @Override
 752    public int getWkt() {
 753        return WKT;
 754    }
 755
 756    @Override
 757    public int getUnsupported() {
 758        return UNSUPPORTED;
 759    }
 760
 761    /**
 762     * Accessor to get the magic string currently being used to trigger
 763     * auto discovery when used as 'type' in a Discovery instance.  Gets
 764     * used in the AddLayer.zul file since ZUL EL can't access static
 765     * variables (booooo!)
 766     * @return
 767     */
 768    @Override
 769    public String getAutoDiscoveryType() {
 770        return AUTO_DISCOVERY_TYPE;
 771    }
 772
 773    /**
 774     * Get the list of supported versions as a human readable string (for
 775     * debugging) - noone outside this class should need to get r/w access
 776     * to the list of versions since they can just test for == UNSUPPORTED
 777     * @return
 778     */
 779    @Override
 780    public String getSupportedVersions() {
 781        StringBuffer sb = new StringBuffer();
 782        boolean once = false;
 783        for (String version : versions) {
 784            if (once) {
 785                sb.append(", ");
 786            }
 787            sb.append("'" + version + "'");
 788            once = true;
 789        }
 790        sb.append(" for automatic discovery, use type '" + AUTO_DISCOVERY_TYPE + "'");
 791        return sb.toString();
 792    }
 793
 794    /**
 795     * Attempt to turn a regular getmap URI into a legend uri,
 796     * then set the default legend uri to the generated value
 797     * @param uri
 798     * @return
 799     */
 800    @Override
 801    public String coerceLegendUri(MapLayer mapLayer) {
 802        String legendUri = getFQUri(mapLayer.getUri());
 803        /* FIXME! Temporary hack to get legend working for thredds -
 804         * we need to send LAYER= and LAYERS= or it returns an
 805         * error
 806         */
 807        if (mapLayer.getType() == LayerUtilities.THREDDS) {
 808            legendUri += queryConjunction(mapLayer.getUri()) + "LAYERS=" + mapLayer.getLayer();
 809        }
 810
 811        return getLegendGraphicUri(legendUri, mapLayer.getLayer(), mapLayer.getImageFormat());
 812
 813    }
 814
 815    /**
 816     * Get the external wms version string for this layer, eg "1.3.0"
 817     * @return the wms version string if this layer supports WMS
 818     * otherwise returns null
 819     */
 820    @Override
 821    public String getWmsVersion(MapLayer mapLayer) {
 822        String version;
 823        if (supportsWms(mapLayer.getType())) {
 824            version = externalVersion(mapLayer.getType());
 825        } else {
 826            version = null;
 827        }
 828        return version;
 829    }
 830
 831    public Settings getSettings() {
 832        return settings;
 833    }
 834
 835    @Required
 836    public void setSettings(Settings settings) {
 837        this.settings = settings;
 838    }
 839
 840    public ResolveHostName getResolveHostname() {
 841        return resolveHostname;
 842    }
 843
 844    @Required
 845    public void setResolveHostname(ResolveHostName resolveHostname) {
 846        this.resolveHostname = resolveHostname;
 847    }
 848
 849    public SettingsSupplementary getSettingsSupplementary() {
 850        return settingsSupplementary;
 851    }
 852
 853    @Required
 854    public void setSettingsSupplementary(SettingsSupplementary settingsSupplementary) {
 855        this.settingsSupplementary = settingsSupplementary;
 856    }
 857
 858    /**
 859     * get bounding box for wms getlayer uri from GetCapabilities response
 860     *
 861     * @param uri wms server get layer uri as String, must contain "layers="
 862     * @return bounding box as List<Double>
 863     */
 864    @Override
 865    public List<Double> getBBox(String uri) {
 866        return getBBoxWCSWFS(uri);
 867        
 868//        try {
 869//            List<Double> bbox = new ArrayList<Double>();
 870//
 871//            //extract server uri
 872//            String server = "";
 873//            int q = uri.indexOf('?');
 874//            if (q > 0) {
 875//                server = uri.substring(0, uri.substring(0, q).lastIndexOf('/') + 1);
 876//            } else {
 877//                server = uri.substring(0, uri.lastIndexOf('/') + 1);
 878//            }
 879//
 880//            //extract layer name
 881//            String name = "";
 882//            int a = uri.toLowerCase().indexOf("layers=");
 883//            if (a > 0) {
 884//                int b = uri.toLowerCase().substring(a, uri.length()).indexOf("&");
 885//                if (b > 0) {
 886//                    //name is between a+len(layer=) and a+b
 887//                    name = uri.substring(a + 7, a + b);
 888//                } else {
 889//                    //name is between a+len(layer=) and len(uri)
 890//                    name = uri.substring(a + 7, uri.length());
 891//                }
 892//            }
 893//
 894//            //don't use gwc/service/ because it is returning the wrong boundingbox
 895//            server = server.replace("gwc/service/","");
 896//
 897//            //make getcapabilities uri
 898//            String wmsget = mangleUriGetCapabilitiesAutoDiscover(server + "wcs", WMS_1_1_0);
 899//
 900//            //get boundingbox for this layer by checking against each title and name
 901//            Document doc = parseXml(wmsget);
 902//            if (doc == null) {
 903//                String wfsget = mangleUriGetCapabilitiesAutoDiscover(server + "wfs", WMS_1_1_0);
 904//                doc = parseXml(wfsget);
 905//                if (doc == null) {
 906//                    return worldBBox;
 907//                }
 908//            }
 909//            NodeList nl = doc.getElementsByTagName("Layers");
 910//            int i, j;
 911//            for (i = 0; i < nl.getLength(); i++) {
 912//                NodeList layer = nl.item(i).getChildNodes();
 913//                boolean match = false;
 914//                for (j = 0; j < layer.getLength(); j++) {
 915//                    if (layer.item(j).getNodeName().equals("Name")
 916//                            || layer.item(j).getNodeName().equals("Title")) {
 917//                        if (layer.item(j).getTextContent().equalsIgnoreCase(name)) {
 918//                            match = true;
 919//                        }
 920//                    } else if (match
 921//                            && (layer.item(j).getNodeName().equals("BoundingBox")
 922//                            || layer.item(j).getNodeName().equals("LatLonBoundingBox"))) {
 923//                        bbox.add(Double.parseDouble(layer.item(j).getAttributes().getNamedItem("minx").getNodeValue()));
 924//                        bbox.add(Double.parseDouble(layer.item(j).getAttributes().getNamedItem("miny").getNodeValue()));
 925//                        bbox.add(Double.parseDouble(layer.item(j).getAttributes().getNamedItem("maxx").getNodeValue()));
 926//                        bbox.add(Double.parseDouble(layer.item(j).getAttributes().getNamedItem("maxy").getNodeValue()));
 927//                        break;
 928//                    }
 929//                }
 930//            }
 931//            return bbox;
 932//        } catch (Exception ex) {
 933//            // java.util.logging.Logger.getLogger(LayerUtilitiesImpl.class.getName()).log(Level.SEVERE, null, ex);
 934//            ex.printStackTrace();
 935//        }
 936//        return worldBBox;
 937    }
 938
 939    public List<Double> getBBoxIndex(String uri) {
 940        //get bounds of layer
 941        List<Double> bbox = new ArrayList(4);
 942
 943        try {
 944            JSONArray layerlist = CommonData.getLayerListJSONArray();
 945
 946            for (int i = 0; i < layerlist.size(); i++) {
 947                JSONObject jo = layerlist.getJSONObject(i);
 948                if (jo.getString("displaypath").equals(uri)) {
 949                    bbox.add(Double.parseDouble(jo.getString("minlongitude")));
 950                    bbox.add(Double.parseDouble(jo.getString("minlatitude")));
 951                    bbox.add(Double.parseDouble(jo.getString("maxlongitude")));
 952                    bbox.add(Double.parseDouble(jo.getString("maxlatitude")));
 953
 954                    return bbox;
 955                }
 956            }
 957        } catch (Exception e) {
 958        }
 959        bbox = getBBox(uri);
 960        return bbox;
 961    }
 962
 963    /**
 964     * parse XML uri to Document
 965     *
 966     * original in: WMSSupportNonXmlBeans.java
 967     */
 968    protected Document parseXml(String discoveryUri) {
 969        boolean parseError = false;
 970        boolean readError = false;
 971        String lastErrorMessage = "";
 972
 973        HttpConnection httpConnection = null;
 974
 975        DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
 976
 977        /*
 978         * Everything on the internet says set the next variable to true but if I
 979         * do this, I can't select the xpath variable I want (which is in another
 980         * namespace) - setting namespace aware to false fixes things...
 981         */
 982        domFactory.setNamespaceAware(false);
 983
 984
 985        /*
 986         * DISABLE DTD Validation
 987         * ======================
 988         * By default, the DTD is processed when we parse the XML and this has the effect
 989         * of setting queryable="0" as a defalt attribute on all layers.  Popular implementations
 990         * (mapserver) just leave off the queryable attribute on layers which ARE queryable, thus
 991         * marking them as non-queryable.
 992         *
 993         * The solution is to totally disable DTD validation, - here's where I found out how to
 994         * do it:
 995         *
 996         * http://stackoverflow.com/questions/582352/how-can-i-ignore-dtd-validation-but-keep-the-doctype-when-writing-an-xml-file
 997         */
 998        //careful... this next line will sneakily re-enable namespaces and break everything
 999        //domFactory.setAttribute("http://xml.org/sax/features/namespaces", true);
1000        domFactory.setAttribute("http://xml.org/sax/features/validation", false);
1001        domFactory.setAttribute("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false);
1002        domFactory.setAttribute("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
1003
1004        DocumentBuilder documentBuilder = null;
1005        Document document = null;
1006        try {
1007            documentBuilder = domFactory.newDocumentBuilder();
1008            documentBuilder.setEntityResolver(null);
1009            document = documentBuilder.parse(discoveryUri);//.parse(is);
1010
1011        } catch (SAXException e) {
1012            parseError = true;
1013            lastErrorMessage = "Unable to parse a GetCapabilities document from '" + discoveryUri
1014                    + "' (parser error - is XML well formed?)";
1015        } catch (ParserConfigurationException e) {
1016            parseError = true;
1017            lastErrorMessage = "Unable to parse a GetCapabilities document from '"
1018                    + discoveryUri + "' (parser configuration error)";
1019        } catch (IOException e) {
1020            readError = true;
1021            // for 404 errors, the message will be the requested url
1022            lastErrorMessage = "IO error connecting to server at '" + discoveryUri + "'.  Root cause: "
1023                    + e.getMessage();
1024        }
1025
1026        // discard broken documents
1027        if (readError || parseError) {
1028            document = null;
1029        }
1030        return document;
1031    }
1032
1033    /**
1034     * get bounding box for wcs or wfs getlayer uri from GetCapabilities response
1035     *
1036     * @param uri wms server get layer uri as String, must contain "layers="
1037     * @return bounding box as List<Double>
1038     */
1039    private List<Double> getBBoxWCSWFS(String uri) {
1040        try {
1041            List<Double> bbox = new ArrayList<Double>();
1042
1043            //extract server uri
1044            String server = "";
1045            int q = uri.indexOf('?');
1046            if (q > 0) {
1047                server = uri.substring(0, uri.substring(0, q).lastIndexOf('/') + 1);
1048            } else {
1049                server = uri.substring(0, uri.lastIndexOf('/') + 1);
1050            }
1051
1052            //extract layer name
1053            String name = "";
1054            int a = uri.toLowerCase().indexOf("layers=");
1055            if (a > 0) {
1056                int b = uri.toLowerCase().substring(a, uri.length()).indexOf("&");
1057                if (b > 0) {
1058                    //name is between a+len(layer=) and a+b
1059                    name = uri.substring(a + 7, a + b);
1060                } else {
1061                    //name is between a+len(layer=) and len(uri)
1062                    name = uri.substring(a + 7, uri.length());
1063                }
1064            }
1065
1066            //don't use gwc/service/ because it is returning the wrong boundingbox
1067            server = server.replace("gwc/service/", "");
1068
1069            //make getcapabilities uri
1070            //String wmsget = mangleUriGetCapabilitiesAutoDiscover(server + "wms", WMS_1_0_0);
1071            LayerUtilitiesImpl wmsUtilities = new LayerUtilitiesImpl();
1072
1073            // strip any existing version,request and service params
1074            String mangled = stripUriRequest(server + "wcs");
1075            mangled = stripUriService(mangled);
1076            mangled = stripUriVersion(mangled);
1077
1078            /* if last char is a '?' or '&' we can append our query
1079             * directly, otherwise we need to append one ourself
1080             */
1081            mangled += queryConjunction(server + "wcs");
1082
1083            // replace with our own params
1084            mangled +=
1085                    "SERVICE=WCS&"
1086                    + "REQUEST=GetCapabilities&"
1087                    + "VERSION=" + wmsUtilities.externalVersion(WMS_1_1_1);
1088
1089            //get boundingbox for this layer by checking against each title and name
1090            HttpClient client = new HttpClient();
1091            GetMethod get = new GetMethod(mangled);
1092            get.addRequestHeader("Accept", "text/plain");
1093            int result = client.executeMethod(get);
1094            String slist = get.getResponseBodyAsString();
1095
1096            int startPos = slist.indexOf("<ows:Title>" + name.replace("ALA:", "") + "</ows:Title>");
1097            if (startPos == -1) {
1098                //attempt to find by identifier
1099                startPos = slist.indexOf("<wcs:Identifier>" + name.replace("ALA:", "") + "</wcs:Identifier>");
1100                //shift startPos backwards to Title
1101                startPos = slist.lastIndexOf("<ows:Title>", startPos);
1102            }
1103
1104            //not found, try WFS
1105            if (startPos == -1) {
1106                //get boundingbox for this layer by checking against each title and name
1107                client = new HttpClient();
1108                get = new GetMethod(mangled.replace("WCS", "WFS").replace("wcs", "wfs"));
1109                get.addRequestHeader("Accept", "text/plain");
1110                result = client.executeMethod(get);
1111                slist = get.getResponseBodyAsString();
1112
1113                startPos = slist.indexOf("<Name>" + name + "</Name>");
1114            }
1115
1116            if (startPos == -1) {
1117                System.out.println("BoundingBox not found for layer: " + name);
1118                return worldBBox;
1119            }
1120
1121            String lc = "ows:LowerCorner>";
1122            String uc = "ows:UpperCorner>";
1123            int lowerCornerPosStart = slist.indexOf(lc, startPos) + lc.length();
1124            int lowerCornerPosEnd = slist.indexOf("</" + lc, lowerCornerPosStart);
1125            int upperCornerPosStart = slist.indexOf(uc, startPos) + uc.length();
1126            int upperCornerPosEnd = slist.indexOf("</" + uc, upperCornerPosStart);
1127
1128            String[] lowerCorner = slist.substring(lowerCornerPosStart, lowerCornerPosEnd).split(" ");
1129            String[] upperCorner = slist.substring(upperCornerPosStart, upperCornerPosEnd).split(" ");
1130
1131            bbox.add(Double.parseDouble(lowerCorner[0]));
1132            bbox.add(Double.parseDouble(lowerCorner[1]));
1133            bbox.add(Double.parseDouble(upperCorner[0]));
1134            bbox.add(Double.parseDouble(upperCorner[1]));
1135
1136            return bbox;
1137        } catch (Exception e) {
1138            return worldBBox;
1139        }
1140    }
1141}