/webportal/src/main/java/au/org/emii/portal/util/LayerUtilitiesImpl.java
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}