Changes in / [13733:13732] in josm
- Location:
- /trunk
- Files:
-
- 1 added
- 7 deleted
- 30 edited
-
data/maps.xsd (modified) (2 diffs)
-
src/org/openstreetmap/josm/actions/AddImageryLayerAction.java (modified) (6 diffs)
-
src/org/openstreetmap/josm/data/cache/JCSCachedTileLoaderJob.java (modified) (7 diffs)
-
src/org/openstreetmap/josm/data/imagery/AbstractWMSTileSource.java (modified) (3 diffs)
-
src/org/openstreetmap/josm/data/imagery/CachedTileLoaderFactory.java (modified) (3 diffs)
-
src/org/openstreetmap/josm/data/imagery/DefaultLayer.java (modified) (3 diffs)
-
src/org/openstreetmap/josm/data/imagery/ImageryInfo.java (modified) (11 diffs)
-
src/org/openstreetmap/josm/data/imagery/LayerDetails.java (deleted)
-
src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoader.java (modified) (4 diffs)
-
src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoaderJob.java (modified) (3 diffs)
-
src/org/openstreetmap/josm/data/imagery/TemplatedWMSTileSource.java (modified) (2 diffs)
-
src/org/openstreetmap/josm/data/imagery/TileJobOptions.java (deleted)
-
src/org/openstreetmap/josm/data/imagery/TileLoaderFactory.java (modified) (1 diff)
-
src/org/openstreetmap/josm/data/imagery/WMSCachedTileLoader.java (modified) (3 diffs)
-
src/org/openstreetmap/josm/data/imagery/WMSCachedTileLoaderJob.java (modified) (2 diffs)
-
src/org/openstreetmap/josm/data/imagery/WMSEndpointTileSource.java (deleted)
-
src/org/openstreetmap/josm/data/imagery/WMTSCapabilities.java (deleted)
-
src/org/openstreetmap/josm/data/imagery/WMTSDefaultLayer.java (added)
-
src/org/openstreetmap/josm/data/imagery/WMTSTileSource.java (modified) (18 diffs)
-
src/org/openstreetmap/josm/gui/bbox/SlippyMapBBoxChooser.java (modified) (2 diffs)
-
src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java (modified) (4 diffs)
-
src/org/openstreetmap/josm/gui/layer/ImageryLayer.java (modified) (1 diff)
-
src/org/openstreetmap/josm/gui/layer/WMSLayer.java (modified) (4 diffs)
-
src/org/openstreetmap/josm/gui/layer/WMTSLayer.java (modified) (2 diffs)
-
src/org/openstreetmap/josm/gui/preferences/imagery/HeadersTable.java (deleted)
-
src/org/openstreetmap/josm/io/imagery/ImageryReader.java (modified) (11 diffs)
-
src/org/openstreetmap/josm/io/imagery/WMSImagery.java (modified) (9 diffs)
-
test/data/wms (deleted)
-
test/unit/org/openstreetmap/josm/TestUtils.java (modified) (3 diffs)
-
test/unit/org/openstreetmap/josm/actions/AddImageryLayerActionTest.java (modified) (1 diff)
-
test/unit/org/openstreetmap/josm/data/cache/HostLimitQueueTest.java (modified) (2 diffs)
-
test/unit/org/openstreetmap/josm/data/cache/JCSCachedTileLoaderJobTest.java (modified) (8 diffs)
-
test/unit/org/openstreetmap/josm/data/imagery/TMSCachedTileLoaderJobTest.java (modified) (3 diffs)
-
test/unit/org/openstreetmap/josm/data/imagery/WMSEndpointTileSourceTest.java (deleted)
-
test/unit/org/openstreetmap/josm/data/imagery/WMTSTileSourceTest.java (modified) (16 diffs)
-
test/unit/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayerTest.java (modified) (1 diff)
-
test/unit/org/openstreetmap/josm/gui/layer/WMTSLayerTest.java (modified) (1 diff)
-
test/unit/org/openstreetmap/josm/io/imagery/WMSImageryTest.java (modified) (5 diffs)
Legend:
- Unmodified
- Added
- Removed
-
/trunk/data/maps.xsd
r13733 r13732 108 108 <!-- TODO: find an easy way to validate projections codes --> 109 109 <xs:element name="code" minOccurs="0" maxOccurs="unbounded" type="xs:string" /> 110 </xs:sequence>111 </xs:complexType>112 113 <xs:complexType name="defaultLayers">114 <xs:sequence>115 <xs:element name="layer" maxOccurs="unbounded">116 <xs:complexType>117 <xs:attribute name="name" type="xs:string" use="optional" />118 <xs:attribute name="style" type="xs:string" use="optional" />119 <xs:attribute name="tileMatrixSet" type="xs:string" use="optional" />120 </xs:complexType>121 </xs:element>122 110 </xs:sequence> 123 111 </xs:complexType> … … 725 713 <!-- old unused feature, ignored --> 726 714 <xs:element name="epsg4326to3857Supported" minOccurs="0" maxOccurs="1" type="xs:boolean" /> 727 <xs:element name="defaultLayers" type="tns:defaultLayers" minOccurs="0" maxOccurs="1" />728 <xs:element name="custom-http-header" minOccurs="0" maxOccurs="unbounded">729 <xs:complexType>730 <xs:attribute name="header-name" type="xs:string" />731 <xs:attribute name="header-value" type="xs:string" />732 </xs:complexType>733 </xs:element>734 <xs:element name="transparent" type="xs:boolean" minOccurs="0" maxOccurs="1"/>735 <xs:element name="format" type="xs:string" minOccurs="0" maxOccurs="1"/>736 715 <!-- does imagery server supports "/status" and tile re-rendering using "/dirty". Defaults to false. --> 737 716 <xs:element name="mod-tile-features" minOccurs="0" maxOccurs="1" type="xs:boolean" /> 738 <!-- minimum time in seconds for which tile will be considered valid -->739 <xs:element name="minimum-tile-expire" minOccurs="0" maxOccurs="1" type="xs:positiveInteger" />740 717 </xs:choice> 741 718 </xs:sequence> -
/trunk/src/org/openstreetmap/josm/actions/AddImageryLayerAction.java
r13733 r13732 13 13 import java.util.ArrayList; 14 14 import java.util.Collection; 15 import java.util.HashSet; 15 16 import java.util.List; 16 import java.util. stream.Collectors;17 import java.util.Set; 17 18 18 19 import javax.swing.JComboBox; … … 26 27 import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType; 27 28 import org.openstreetmap.josm.data.imagery.WMTSTileSource; 28 import org.openstreetmap.josm.data.imagery.WMTSTileSource.WMTSGetCapabilitiesException;29 29 import org.openstreetmap.josm.gui.ExtendedDialog; 30 30 import org.openstreetmap.josm.gui.layer.AlignImageryPanel; … … 34 34 import org.openstreetmap.josm.gui.util.GuiHelper; 35 35 import org.openstreetmap.josm.io.imagery.WMSImagery; 36 import org.openstreetmap.josm.io.imagery.WMSImagery.LayerDetails; 36 37 import org.openstreetmap.josm.io.imagery.WMSImagery.WMSGetCapabilitiesException; 37 38 import org.openstreetmap.josm.tools.CheckParameterUtil; … … 95 96 case WMS_ENDPOINT: 96 97 // convert to WMS type 97 if (info.getDefaultLayers() == null || info.getDefaultLayers().isEmpty()) { 98 return getWMSLayerInfo(info); 99 } else { 100 return info; 101 } 98 return getWMSLayerInfo(info); 102 99 case WMTS: 103 100 // specify which layer to use 104 if (info.getDefaultLayers() == null || info.getDefaultLayers().isEmpty()) { 105 DefaultLayer layerId = new WMTSTileSource(info).userSelectLayer(); 106 if (layerId != null) { 107 ImageryInfo copy = new ImageryInfo(info); 108 List<DefaultLayer> defaultLayers = new ArrayList<>(1); 109 defaultLayers.add(layerId); 110 copy.setDefaultLayers(defaultLayers); 111 return copy; 112 } 113 return null; 114 } else { 115 return info; 101 DefaultLayer layerId = new WMTSTileSource(info).userSelectLayer(); 102 if (layerId != null) { 103 ImageryInfo copy = new ImageryInfo(info); 104 Collection<DefaultLayer> defaultLayers = new ArrayList<>(1); 105 defaultLayers.add(layerId); 106 copy.setDefaultLayers(defaultLayers); 107 return copy; 116 108 } 109 // layer not selected - refuse to add 110 return null; 117 111 default: 118 112 return info; … … 136 130 } 137 131 Logging.log(Logging.LEVEL_ERROR, "Could not parse WMS layer list. Incoming data:\n"+ex.getIncomingData(), ex); 138 } catch (WMTSGetCapabilitiesException e) {139 if (!GraphicsEnvironment.isHeadless()) {140 JOptionPane.showMessageDialog(Main.parent, tr("Could not parse WMTS layer list."),141 tr("WMS Error"), JOptionPane.ERROR_MESSAGE);142 }143 Logging.log(Logging.LEVEL_ERROR, "Could not parse WMTS layer list.", e);144 132 } 145 133 return null; … … 178 166 */ 179 167 protected static ImageryInfo getWMSLayerInfo(ImageryInfo info) throws IOException, WMSGetCapabilitiesException { 180 try { 181 CheckParameterUtil.ensureThat(ImageryType.WMS_ENDPOINT.equals(info.getImageryType()), "wms_endpoint imagery type expected"); 182 final WMSImagery wms = new WMSImagery(info.getUrl()); 183 184 final WMSLayerTree tree = new WMSLayerTree(); 185 tree.updateTree(wms); 186 187 Collection<String> wmsFormats = wms.getFormats(); 188 final JComboBox<String> formats = new JComboBox<>(wmsFormats.toArray(new String[wmsFormats.size()])); 189 formats.setSelectedItem(wms.getPreferredFormat()); 190 formats.setToolTipText(tr("Select image format for WMS layer")); 191 192 if (!GraphicsEnvironment.isHeadless()) { 193 if (1 != new ExtendedDialog(Main.parent, tr("Select WMS layers"), new String[]{tr("Add layers"), tr("Cancel")}) { { 194 final JScrollPane scrollPane = new JScrollPane(tree.getLayerTree()); 195 scrollPane.setPreferredSize(new Dimension(400, 400)); 196 final JPanel panel = new JPanel(new GridBagLayout()); 197 panel.add(scrollPane, GBC.eol().fill()); 198 panel.add(formats, GBC.eol().fill(GBC.HORIZONTAL)); 199 setContent(panel); 200 } }.showDialog().getValue()) { 201 return null; 202 } 203 } 204 205 final String url = wms.buildGetMapUrl( 206 tree.getSelectedLayers().stream().map(x -> x.getName()).collect(Collectors.toList()), 207 (List<String>) null, 208 (String) formats.getSelectedItem(), 209 true // TODO: ask the user if (s)he wants transparent layer 210 ); 211 212 String selectedLayers = tree.getSelectedLayers().stream() 213 .map(x -> x.getName()) 214 .collect(Collectors.joining(", ")); 215 ImageryInfo ret = new ImageryInfo(info.getName() + selectedLayers, 216 url, 217 "wms", 218 info.getEulaAcceptanceRequired(), 219 info.getCookies()); 220 221 ret.setServerProjections(wms.getServerProjections(tree.getSelectedLayers())); 222 223 return ret; 224 } catch (MalformedURLException ex) { 225 if (!GraphicsEnvironment.isHeadless()) { 226 JOptionPane.showMessageDialog(Main.parent, tr("Invalid service URL."), 227 tr("WMS Error"), JOptionPane.ERROR_MESSAGE); 228 } 229 Logging.log(Logging.LEVEL_ERROR, ex); 230 } catch (IOException ex) { 231 if (!GraphicsEnvironment.isHeadless()) { 232 JOptionPane.showMessageDialog(Main.parent, tr("Could not retrieve WMS layer list."), 233 tr("WMS Error"), JOptionPane.ERROR_MESSAGE); 234 } 235 Logging.log(Logging.LEVEL_ERROR, ex); 236 } catch (WMSGetCapabilitiesException ex) { 237 if (!GraphicsEnvironment.isHeadless()) { 238 JOptionPane.showMessageDialog(Main.parent, tr("Could not parse WMS layer list."), 239 tr("WMS Error"), JOptionPane.ERROR_MESSAGE); 240 } 241 Logging.log(Logging.LEVEL_ERROR, "Could not parse WMS layer list. Incoming data:\n"+ex.getIncomingData(), ex); 242 } 243 return null; 168 CheckParameterUtil.ensureThat(ImageryType.WMS_ENDPOINT.equals(info.getImageryType()), "wms_endpoint imagery type expected"); 169 170 final WMSImagery wms = new WMSImagery(); 171 wms.attemptGetCapabilities(info.getUrl()); 172 173 final WMSLayerTree tree = new WMSLayerTree(); 174 tree.updateTree(wms); 175 List<String> wmsFormats = wms.getFormats(); 176 final JComboBox<String> formats = new JComboBox<>(wmsFormats.toArray(new String[0])); 177 formats.setSelectedItem(wms.getPreferredFormats()); 178 formats.setToolTipText(tr("Select image format for WMS layer")); 179 180 if (!GraphicsEnvironment.isHeadless() && 1 != new SelectWmsLayersDialog(tree, formats).showDialog().getValue()) { 181 return null; 182 } 183 184 final String url = wms.buildGetMapUrl( 185 tree.getSelectedLayers(), (String) formats.getSelectedItem()); 186 Set<String> supportedCrs = new HashSet<>(); 187 boolean first = true; 188 StringBuilder layersString = new StringBuilder(); 189 for (LayerDetails layer: tree.getSelectedLayers()) { 190 if (first) { 191 supportedCrs.addAll(layer.getProjections()); 192 first = false; 193 } 194 layersString.append(layer.name); 195 layersString.append(", "); 196 supportedCrs.retainAll(layer.getProjections()); 197 } 198 199 // copy all information from WMS 200 ImageryInfo ret = new ImageryInfo(info); 201 // and update according to user choice 202 ret.setUrl(url); 203 ret.setImageryType(ImageryType.WMS); 204 if (layersString.length() > 2) { 205 ret.setName(ret.getName() + ' ' + layersString.substring(0, layersString.length() - 2)); 206 } 207 ret.setServerProjections(supportedCrs); 208 return ret; 244 209 } 245 210 -
/trunk/src/org/openstreetmap/josm/data/cache/JCSCachedTileLoaderJob.java
r13733 r13732 21 21 import org.apache.commons.jcs.engine.behavior.ICacheElement; 22 22 import org.openstreetmap.josm.data.cache.ICachedLoaderListener.LoadResult; 23 import org.openstreetmap.josm.data.imagery.TileJobOptions;24 23 import org.openstreetmap.josm.data.preferences.IntegerProperty; 25 24 import org.openstreetmap.josm.tools.CheckParameterUtil; … … 94 93 private Runnable finishTask; 95 94 private boolean force; 96 private long minimumExpiryTime;97 95 98 96 /** 99 97 * @param cache cache instance that we will work on 100 * @param options options of the request 98 * @param headers HTTP headers to be sent together with request 99 * @param readTimeout when connecting to remote resource 100 * @param connectTimeout when connecting to remote resource 101 101 * @param downloadJobExecutor that will be executing the jobs 102 102 */ 103 103 public JCSCachedTileLoaderJob(ICacheAccess<K, V> cache, 104 TileJobOptions options, 104 int connectTimeout, int readTimeout, 105 Map<String, String> headers, 105 106 ThreadPoolExecutor downloadJobExecutor) { 106 107 CheckParameterUtil.ensureParameterNotNull(cache, "cache"); 107 108 this.cache = cache; 108 109 this.now = System.currentTimeMillis(); 109 this.connectTimeout = options.getConnectionTimeout();110 this.readTimeout = options.getReadTimeout();111 this.headers = options.getHeaders();110 this.connectTimeout = connectTimeout; 111 this.readTimeout = readTimeout; 112 this.headers = headers; 112 113 this.downloadJobExecutor = downloadJobExecutor; 113 this.minimumExpiryTime = TimeUnit.SECONDS.toMillis(options.getMinimumExpiryTime());114 114 } 115 115 116 116 /** 117 117 * @param cache cache instance that we will work on 118 * @param options of the request 118 * @param headers HTTP headers to be sent together with request 119 * @param readTimeout when connecting to remote resource 120 * @param connectTimeout when connecting to remote resource 119 121 */ 120 122 public JCSCachedTileLoaderJob(ICacheAccess<K, V> cache, 121 TileJobOptions options) { 122 this(cache, options, DEFAULT_DOWNLOAD_JOB_DISPATCHER); 123 int connectTimeout, int readTimeout, 124 Map<String, String> headers) { 125 this(cache, connectTimeout, readTimeout, 126 headers, DEFAULT_DOWNLOAD_JOB_DISPATCHER); 123 127 } 124 128 … … 274 278 // put a limit to the expire time (some servers send a value 275 279 // that is too large) 276 expires = Math.min(expires, attributes.getCreateTime() + Math.max(EXPIRE_TIME_SERVER_LIMIT, minimumExpiryTime));280 expires = Math.min(expires, attributes.getCreateTime() + EXPIRE_TIME_SERVER_LIMIT); 277 281 if (now > expires) { 278 282 Logging.debug("JCS - Object {0} has expired -> valid to {1}, now is: {2}", … … 281 285 } 282 286 } else if (attributes.getLastModification() > 0 && 283 now - attributes.getLastModification() > Math.max(DEFAULT_EXPIRE_TIME, minimumExpiryTime)) {287 now - attributes.getLastModification() > DEFAULT_EXPIRE_TIME) { 284 288 // check by file modification date 285 289 Logging.debug("JCS - Object has expired, maximum file age reached {0}", getUrlNoException()); 286 290 return false; 287 } else if (now - attributes.getCreateTime() > Math.max(DEFAULT_EXPIRE_TIME, minimumExpiryTime)) {291 } else if (now - attributes.getCreateTime() > DEFAULT_EXPIRE_TIME) { 288 292 Logging.debug("JCS - Object has expired, maximum time since object creation reached {0}", getUrlNoException()); 289 293 return false; … … 326 330 // and the server answers with a HTTP 304 = "Not Modified" 327 331 Logging.debug("JCS - If-Modified-Since/ETag test: local version is up to date: {0}", getUrl()); 328 // update cache attributes329 attributes = parseHeaders(urlConn);330 cache.put(getCacheKey(), cacheData, attributes);331 332 return true; 332 333 } else if (isObjectLoadable() // we have an object in cache, but we haven't received 304 response code … … 452 453 Logging.trace(e); 453 454 } 454 if (lng.equals(0L)) { 455 lng = System.currentTimeMillis() + DEFAULT_EXPIRE_TIME; 456 } 457 } 458 459 ret.setExpirationTime(Math.max(minimumExpiryTime + System.currentTimeMillis(), lng)); 455 } 456 457 ret.setExpirationTime(lng); 460 458 ret.setLastModification(now); 461 459 ret.setEtag(urlConn.getHeaderField("ETag")); … … 482 480 final HttpClient.Response urlConn = getRequest("HEAD", false).connect(); 483 481 long lastModified = urlConn.getLastModified(); 484 boolean ret =(attributes.getEtag() != null && attributes.getEtag().equals(urlConn.getHeaderField("ETag"))) ||482 return (attributes.getEtag() != null && attributes.getEtag().equals(urlConn.getHeaderField("ETag"))) || 485 483 (lastModified != 0 && lastModified <= attributes.getLastModification()); 486 if (ret) {487 // update attributes488 attributes = parseHeaders(urlConn);489 cache.put(getCacheKey(), cacheData, attributes);490 }491 return ret;492 484 } 493 485 -
/trunk/src/org/openstreetmap/josm/data/imagery/AbstractWMSTileSource.java
r13733 r13732 3 3 4 4 import java.awt.Point; 5 import java.text.DecimalFormat;6 import java.text.DecimalFormatSymbols;7 import java.text.NumberFormat;8 import java.util.Locale;9 5 10 6 import org.openstreetmap.gui.jmapviewer.Projected; … … 28 24 public abstract class AbstractWMSTileSource extends TMSTileSource { 29 25 30 static final NumberFormat LATLON_FORMAT = new DecimalFormat("###0.0000000", new DecimalFormatSymbols(Locale.US));31 32 26 private EastNorth anchorPosition; 33 27 private int[] tileXMin; … … 215 209 return this.tileProjection.toCode(); 216 210 } 217 218 protected String getBbox(int zoom, int tilex, int tiley, boolean switchLatLon) {219 EastNorth nw = getTileEastNorth(tilex, tiley, zoom);220 EastNorth se = getTileEastNorth(tilex + 1, tiley + 1, zoom);221 222 double w = nw.getX();223 double n = nw.getY();224 225 double s = se.getY();226 double e = se.getX();227 228 return (229 switchLatLon ?230 String.format("%s,%s,%s,%s",231 LATLON_FORMAT.format(s), LATLON_FORMAT.format(w), LATLON_FORMAT.format(n), LATLON_FORMAT.format(e))232 :233 String.format("%s,%s,%s,%s",234 LATLON_FORMAT.format(w), LATLON_FORMAT.format(s), LATLON_FORMAT.format(e), LATLON_FORMAT.format(n))235 236 );237 }238 211 } -
/trunk/src/org/openstreetmap/josm/data/imagery/CachedTileLoaderFactory.java
r13733 r13732 44 44 TileLoaderListener.class, 45 45 ICacheAccess.class, 46 TileJobOptions.class 47 ); 46 int.class, 47 int.class, 48 Map.class); 48 49 } catch (NoSuchMethodException | SecurityException e) { 49 50 Logging.log(Logging.LEVEL_WARN, "Unable to initialize cache tile loader factory", e); … … 63 64 64 65 @Override 65 public TileLoader makeTileLoader(TileLoaderListener listener, Map<String, String> inputHeaders , long minimumExpiryTime) {66 public TileLoader makeTileLoader(TileLoaderListener listener, Map<String, String> inputHeaders) { 66 67 Map<String, String> headers = new ConcurrentHashMap<>(); 67 68 headers.put("User-Agent", Version.getInstance().getFullAgentString()); … … 71 72 72 73 return getLoader(listener, cache, 73 new TileJobOptions( 74 (int) TimeUnit.SECONDS.toMillis(Config.getPref().getInt("socket.timeout.connect", 15)), 75 (int) TimeUnit.SECONDS.toMillis(Config.getPref().getInt("socket.timeout.read", 30)), 76 headers, 77 minimumExpiryTime 78 ) 79 ); 74 (int) TimeUnit.SECONDS.toMillis(Config.getPref().getInt("socket.timeout.connect", 15)), 75 (int) TimeUnit.SECONDS.toMillis(Config.getPref().getInt("socket.timeout.read", 30)), 76 headers); 80 77 } 81 78 82 79 protected TileLoader getLoader(TileLoaderListener listener, ICacheAccess<String, BufferedImageCacheEntry> cache, 83 TileJobOptions options) {80 int connectTimeout, int readTimeout, Map<String, String> headers) { 84 81 try { 85 82 return tileLoaderConstructor.newInstance( 86 83 listener, 87 84 cache, 88 options 89 ); 85 connectTimeout, 86 readTimeout, 87 headers); 90 88 } catch (IllegalArgumentException e) { 91 89 Logging.warn(e); -
/trunk/src/org/openstreetmap/josm/data/imagery/DefaultLayer.java
r13733 r13732 1 1 // License: GPL. For details, see LICENSE file. 2 2 package org.openstreetmap.josm.data.imagery; 3 4 import static org.openstreetmap.josm.tools.I18n.tr;5 6 import javax.json.Json;7 import javax.json.JsonObject;8 import javax.json.JsonObjectBuilder;9 10 import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType;11 3 12 4 /** … … 20 12 */ 21 13 public class DefaultLayer { 22 private final String layerName; 23 private final String tileMatrixSet; 24 private final String style; 14 15 protected String layerName; 25 16 26 17 /** 27 18 * Constructor 28 * @param imageryType for which this layer is defined 29 * @param layerName as returned by getIdentifier for WMTS and getName for WMS 30 * @param style of the layer 31 * @param tileMatrixSet only for WMTS - tileMatrixSet to use 19 * @param layerName that is the DefaultLayer 32 20 */ 33 public DefaultLayer(ImageryType imageryType, String layerName, String style, String tileMatrixSet) { 34 this.layerName = layerName == null ? "" : layerName; 35 this.style = style == null ? "" : style; 36 if (!imageryType.equals(ImageryType.WMTS) && !(tileMatrixSet == null || "".equals(tileMatrixSet))) { 37 throw new IllegalArgumentException(tr("{0} imagery has tileMatrixSet defined to: {1}", imageryType, tileMatrixSet)); 38 } 39 this.tileMatrixSet = tileMatrixSet == null ? "" : tileMatrixSet; 21 public DefaultLayer(String layerName) { 22 this.layerName = layerName; 40 23 } 41 24 … … 47 30 } 48 31 49 /**50 * @return default tileMatrixSet. Only usable for WMTS51 */52 public String getTileMatrixSet() {53 return tileMatrixSet;54 }55 56 /**57 * @return style for this WMS / WMTS layer to use58 */59 public String getStyle() {60 return style;61 }62 63 /**64 * @return JSON representation of the default layer object65 */66 public JsonObject toJson() {67 JsonObjectBuilder ret = Json.createObjectBuilder();68 ret.add("layerName", layerName);69 ret.add("style", style);70 ret.add("tileMatrixSet", tileMatrixSet);71 return ret.build();72 }73 74 /**75 * Factory method creating DefaultLayer from JSON objects76 * @param o serialized DefaultLayer object77 * @param type of ImageryType serialized78 * @return DefaultLayer instance based on JSON object79 */80 public static DefaultLayer fromJson(JsonObject o, ImageryType type) {81 return new DefaultLayer(type, o.getString("layerName"), o.getString("style"), o.getString("tileMatrixSet"));82 }83 32 } -
/trunk/src/org/openstreetmap/josm/data/imagery/ImageryInfo.java
r13733 r13732 5 5 6 6 import java.awt.Image; 7 import java.io.StringReader;8 7 import java.util.ArrayList; 9 8 import java.util.Arrays; … … 16 15 import java.util.Set; 17 16 import java.util.TreeSet; 18 import java.util.concurrent.ConcurrentHashMap;19 import java.util.concurrent.TimeUnit;20 17 import java.util.regex.Matcher; 21 18 import java.util.regex.Pattern; 22 19 import java.util.stream.Collectors; 23 20 24 import javax.json.Json;25 import javax.json.JsonObject;26 import javax.json.stream.JsonCollectors;27 21 import javax.swing.ImageIcon; 28 22 … … 224 218 private boolean isGeoreferenceValid; 225 219 /** which layers should be activated by default on layer addition. **/ 226 private List<DefaultLayer> defaultLayers = new ArrayList<>(); 227 /** HTTP headers **/ 228 private Map<String, String> customHttpHeaders = new ConcurrentHashMap<>(); 229 /** Should this map be transparent **/ 230 private boolean transparent = true; 231 private int minimumTileExpire = (int) TimeUnit.MILLISECONDS.toSeconds(TMSCachedTileLoaderJob.MINIMUM_EXPIRES.get()); 232 /** when adding a field, also adapt the: 233 * {@link #ImageryPreferenceEntry ImageryPreferenceEntry object} 234 * {@link #ImageryPreferenceEntry#ImageryPreferenceEntry(ImageryInfo) ImageryPreferenceEntry constructor} 235 * {@link #ImageryInfo(ImageryPreferenceEntry) ImageryInfo constructor} 236 * {@link #ImageryInfo(ImageryInfo) ImageryInfo constructor} 237 * {@link #equalsPref(ImageryPreferenceEntry) equalsPref method} 238 **/ 220 private Collection<DefaultLayer> defaultLayers = Collections.emptyList(); 221 // when adding a field, also adapt the ImageryInfo(ImageryInfo) 222 // and ImageryInfo(ImageryPreferenceEntry) constructor, equals method, and ImageryPreferenceEntry 239 223 240 224 /** … … 274 258 @StructEntry boolean modTileFeatures; 275 259 @StructEntry boolean overlay; 276 @StructEntry String default_layers; 277 @StructEntry Map<String, String> customHttpHeaders; 278 @StructEntry boolean transparent; 279 @StructEntry int minimumTileExpire; 260 // TODO: disabled until change of layers is implemented 261 // @StructEntry String default_layers; 280 262 281 263 /** … … 326 308 } 327 309 } 328 if (!i.serverProjections.isEmpty()) { 329 projections = i.serverProjections.stream().collect(Collectors.joining(",")); 330 } 310 projections = i.serverProjections.stream().collect(Collectors.joining(",")); 331 311 if (i.noTileHeaders != null && !i.noTileHeaders.isEmpty()) { 332 312 noTileHeaders = new MultiMap<>(i.noTileHeaders); … … 345 325 valid_georeference = i.isGeoreferenceValid(); 346 326 modTileFeatures = i.isModTileFeatures(); 347 if (!i.defaultLayers.isEmpty()) { 348 default_layers = i.defaultLayers.stream().map(x -> x.toJson()).collect(JsonCollectors.toJsonArray()).toString(); 349 } 350 customHttpHeaders = i.customHttpHeaders; 351 transparent = i.isTransparent(); 352 minimumTileExpire = i.minimumTileExpire; 327 // TODO disabled until change of layers is implemented 328 // default_layers = i.defaultLayers.stream().collect(Collectors.joining(",")); 353 329 } 354 330 … … 492 468 isGeoreferenceValid = e.valid_georeference; 493 469 modTileFeatures = e.modTileFeatures; 494 if (e.default_layers != null) { 495 defaultLayers = Json.createReader(new StringReader(e.default_layers)). 496 readArray(). 497 stream(). 498 map(x -> DefaultLayer.fromJson((JsonObject) x, imageryType)). 499 collect(Collectors.toList()); 500 } 501 customHttpHeaders = e.customHttpHeaders; 502 transparent = e.transparent; 503 minimumTileExpire = e.minimumTileExpire; 470 // TODO disabled until change of layers is implemented 471 // defaultLayers = Arrays.asList(e.default_layers.split(",")); 504 472 } 505 473 … … 546 514 this.isGeoreferenceValid = i.isGeoreferenceValid; 547 515 this.defaultLayers = i.defaultLayers; 548 this.customHttpHeaders = i.customHttpHeaders;549 this.transparent = i.transparent;550 this.minimumTileExpire = i.minimumTileExpire;551 516 } 552 517 … … 600 565 Objects.equals(this.noTileChecksums, other.noTileChecksums) && 601 566 Objects.equals(this.metadataHeaders, other.metadataHeaders) && 602 Objects.equals(this.defaultLayers, other.defaultLayers) && 603 Objects.equals(this.customHttpHeaders, other.customHttpHeaders) && 604 Objects.equals(this.transparent, other.transparent) && 605 Objects.equals(this.minimumTileExpire, other.minimumTileExpire); 567 Objects.equals(this.defaultLayers, other.defaultLayers); 606 568 // CHECKSTYLE.ON: BooleanExpressionComplexity 607 569 } … … 1410 1372 * @return Collection of the layer names 1411 1373 */ 1412 public List<DefaultLayer> getDefaultLayers() {1374 public Collection<DefaultLayer> getDefaultLayers() { 1413 1375 return defaultLayers; 1414 1376 } … … 1418 1380 * @param layers set the list of default layers 1419 1381 */ 1420 public void setDefaultLayers(List<DefaultLayer> layers) { 1382 public void setDefaultLayers(Collection<DefaultLayer> layers) { 1383 if (ImageryType.WMTS.equals(this.imageryType)) { 1384 CheckParameterUtil.ensureThat(layers == null || 1385 layers.isEmpty() || 1386 layers.iterator().next() instanceof WMTSDefaultLayer, "Incorrect default layer"); 1387 } 1421 1388 this.defaultLayers = layers; 1422 1389 } 1423 1424 /**1425 * Returns custom HTTP headers that should be sent with request towards imagery provider1426 * @return headers1427 */1428 public Map<String, String> getCustomHttpHeaders() {1429 return customHttpHeaders;1430 }1431 1432 /**1433 * Sets custom HTTP headers that should be sent with request towards imagery provider1434 * @param customHttpHeaders1435 */1436 public void setCustomHttpHeaders(Map<String, String> customHttpHeaders) {1437 this.customHttpHeaders = customHttpHeaders;1438 }1439 1440 /**1441 * @return should this imagery be transparent1442 */1443 public boolean isTransparent() {1444 return transparent;1445 }1446 1447 /**1448 *1449 * @param transparent set to true if imagery should be transparent1450 */1451 public void setTransparent(boolean transparent) {1452 this.transparent = transparent;1453 }1454 1455 /**1456 * @return minimum tile expiration in seconds1457 */1458 public int getMinimumTileExpire() {1459 return minimumTileExpire;1460 }1461 1462 /**1463 * Sets minimum tile expiration in seconds1464 * @param minimumTileExpire1465 */1466 public void setMinimumTileExpire(int minimumTileExpire) {1467 this.minimumTileExpire = minimumTileExpire;1468 1469 }1470 1390 } -
/trunk/src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoader.java
r13733 r13732 2 2 package org.openstreetmap.josm.data.imagery; 3 3 4 import java.util.Map; 4 5 import java.util.concurrent.ThreadPoolExecutor; 5 6 import java.util.concurrent.TimeUnit; … … 26 27 27 28 protected final ICacheAccess<String, BufferedImageCacheEntry> cache; 29 protected final int connectTimeout; 30 protected final int readTimeout; 31 protected final Map<String, String> headers; 28 32 protected final TileLoaderListener listener; 29 33 … … 46 50 47 51 private ThreadPoolExecutor downloadExecutor = DEFAULT_DOWNLOAD_JOB_DISPATCHER; 48 protected final TileJobOptions options;49 52 50 53 /** 51 54 * Constructor 52 55 * @param listener called when tile loading has finished 53 * @param cache of the cache 54 * @param options tile job options 56 * @param cache of the cache 57 * @param connectTimeout to remote resource 58 * @param readTimeout to remote resource 59 * @param headers HTTP headers to be sent along with request 55 60 */ 56 61 public TMSCachedTileLoader(TileLoaderListener listener, ICacheAccess<String, BufferedImageCacheEntry> cache, 57 TileJobOptions options) {62 int connectTimeout, int readTimeout, Map<String, String> headers) { 58 63 CheckParameterUtil.ensureParameterNotNull(cache, "cache"); 59 64 this.cache = cache; 60 this.options = options; 65 this.connectTimeout = connectTimeout; 66 this.readTimeout = readTimeout; 67 this.headers = headers; 61 68 this.listener = listener; 62 69 } … … 91 98 @Override 92 99 public TileJob createTileLoaderJob(Tile tile) { 93 return new TMSCachedTileLoaderJob( 94 listener, 95 tile, 96 cache, 97 options, 98 getDownloadExecutor()); 100 return new TMSCachedTileLoaderJob(listener, tile, cache, 101 connectTimeout, readTimeout, headers, getDownloadExecutor()); 99 102 } 100 103 -
/trunk/src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoaderJob.java
r13733 r13732 44 44 */ 45 45 public class TMSCachedTileLoaderJob extends JCSCachedTileLoaderJob<String, BufferedImageCacheEntry> implements TileJob, ICachedLoaderListener { 46 /** General maximum expires for tiles. Might be overridden by imagery settings */ 47 public static final LongProperty MAXIMUM_EXPIRES = new LongProperty("imagery.generic.maximum_expires", TimeUnit.DAYS.toMillis(30)); 48 /** General minimum expires for tiles. Might be overridden by imagery settings */ 49 public static final LongProperty MINIMUM_EXPIRES = new LongProperty("imagery.generic.minimum_expires", TimeUnit.HOURS.toMillis(1)); 46 private static final LongProperty MAXIMUM_EXPIRES = new LongProperty("imagery.generic.maximum_expires", TimeUnit.DAYS.toMillis(30)); 47 private static final LongProperty MINIMUM_EXPIRES = new LongProperty("imagery.generic.minimum_expires", TimeUnit.HOURS.toMillis(1)); 50 48 static final Pattern SERVICE_EXCEPTION_PATTERN = Pattern.compile("(?s).+<ServiceException[^>]*>(.+)</ServiceException>.+"); 51 49 protected final Tile tile; 52 50 private volatile URL url; 53 private final TileJobOptions options;54 51 55 52 // we need another deduplication of Tile Loader listeners, as for each submit, new TMSCachedTileLoaderJob was created … … 62 59 * @param tile to be fetched from cache 63 60 * @param cache object 64 * @param options for job (such as http headers, timeouts etc.) 61 * @param connectTimeout when connecting to remote resource 62 * @param readTimeout when connecting to remote resource 63 * @param headers HTTP headers to be sent together with request 65 64 * @param downloadExecutor that will be executing the jobs 66 65 */ 67 66 public TMSCachedTileLoaderJob(TileLoaderListener listener, Tile tile, 68 67 ICacheAccess<String, BufferedImageCacheEntry> cache, 69 TileJobOptions options,68 int connectTimeout, int readTimeout, Map<String, String> headers, 70 69 ThreadPoolExecutor downloadExecutor) { 71 super(cache, options, downloadExecutor);70 super(cache, connectTimeout, readTimeout, headers, downloadExecutor); 72 71 this.tile = tile; 73 this.options = options;74 72 if (listener != null) { 75 73 String deduplicationKey = getCacheKey(); … … 247 245 // keep the expiration time between MINIMUM_EXPIRES and MAXIMUM_EXPIRES, so we will cache the tiles 248 246 // at least for some short period of time, but not too long 249 if (ret.getExpirationTime() < now + M ath.max(MINIMUM_EXPIRES.get(), options.getMinimumExpiryTime())) {247 if (ret.getExpirationTime() < now + MINIMUM_EXPIRES.get()) { 250 248 ret.setExpirationTime(now + MINIMUM_EXPIRES.get()); 251 249 } 252 if (ret.getExpirationTime() > now + M ath.max(MAXIMUM_EXPIRES.get(), options.getMinimumExpiryTime())) {250 if (ret.getExpirationTime() > now + MAXIMUM_EXPIRES.get()) { 253 251 ret.setExpirationTime(now + MAXIMUM_EXPIRES.get()); 254 252 } -
/trunk/src/org/openstreetmap/josm/data/imagery/TemplatedWMSTileSource.java
r13733 r13732 59 59 super(info, tileProjection); 60 60 this.serverProjections = new TreeSet<>(info.getServerProjections()); 61 this.headers.putAll(info.getCustomHttpHeaders());62 61 handleTemplate(); 63 62 initProjection(); … … 110 109 switchLatLon = Main.getProjection().switchXY(); 111 110 } 112 String bbox = getBbox(zoom, tilex, tiley, switchLatLon); 111 String bbox; 112 if (switchLatLon) { 113 bbox = String.format("%s,%s,%s,%s", 114 LATLON_FORMAT.format(s), LATLON_FORMAT.format(w), LATLON_FORMAT.format(n), LATLON_FORMAT.format(e)); 115 } else { 116 bbox = String.format("%s,%s,%s,%s", 117 LATLON_FORMAT.format(w), LATLON_FORMAT.format(s), LATLON_FORMAT.format(e), LATLON_FORMAT.format(n)); 118 } 113 119 114 120 // Using StringBuffer and generic PATTERN_PARAM matcher gives 2x performance improvement over replaceAll -
/trunk/src/org/openstreetmap/josm/data/imagery/TileLoaderFactory.java
r13733 r13732 19 19 * @param listener that will be notified, when tile has finished loading 20 20 * @param headers that will be sent with requests to TileSource. <code>null</code> indicates none 21 * @param minimumExpiryTime minimum expiry time22 21 * @return TileLoader that uses both of above 23 22 */ 24 TileLoader makeTileLoader(TileLoaderListener listener, Map<String, String> headers , long minimumExpiryTime);23 TileLoader makeTileLoader(TileLoaderListener listener, Map<String, String> headers); 25 24 } -
/trunk/src/org/openstreetmap/josm/data/imagery/WMSCachedTileLoader.java
r13733 r13732 1 1 // License: GPL. For details, see LICENSE file. 2 2 package org.openstreetmap.josm.data.imagery; 3 4 import java.util.Map; 3 5 4 6 import org.apache.commons.jcs.access.behavior.ICacheAccess; … … 33 35 */ 34 36 public WMSCachedTileLoader(TileLoaderListener listener, ICacheAccess<String, BufferedImageCacheEntry> cache, 35 TileJobOptions options) {37 int connectTimeout, int readTimeout, Map<String, String> headers) { 36 38 37 super(listener, cache, options);39 super(listener, cache, connectTimeout, readTimeout, headers); 38 40 setDownloadExecutor(TMSCachedTileLoader.getNewThreadPoolExecutor("WMS-downloader-%d", THREAD_LIMIT.get())); 39 41 } … … 41 43 @Override 42 44 public TileJob createTileLoaderJob(Tile tile) { 43 return new WMSCachedTileLoaderJob(listener, tile, cache, options, getDownloadExecutor());45 return new WMSCachedTileLoaderJob(listener, tile, cache, connectTimeout, readTimeout, headers, getDownloadExecutor()); 44 46 } 45 47 } -
/trunk/src/org/openstreetmap/josm/data/imagery/WMSCachedTileLoaderJob.java
r13733 r13732 2 2 package org.openstreetmap.josm.data.imagery; 3 3 4 import java.util.Map; 4 5 import java.util.concurrent.ThreadPoolExecutor; 5 6 … … 22 23 * @param tile to load 23 24 * @param cache to use (get/put) 24 * @param options options for tile job 25 * @param connectTimeout to tile source 26 * @param readTimeout to tile source 27 * @param headers to be sent with request 25 28 * @param downloadExecutor that will execute the download task (if needed) 26 29 */ 27 public WMSCachedTileLoaderJob(TileLoaderListener listener, 28 Tile tile, 29 ICacheAccess<String, BufferedImageCacheEntry> cache, 30 TileJobOptions options, 31 ThreadPoolExecutor downloadExecutor) { 32 super(listener, tile, cache, options, downloadExecutor); 30 public WMSCachedTileLoaderJob(TileLoaderListener listener, Tile tile, 31 ICacheAccess<String, BufferedImageCacheEntry> cache, int connectTimeout, int readTimeout, 32 Map<String, String> headers, ThreadPoolExecutor downloadExecutor) { 33 super(listener, tile, cache, connectTimeout, readTimeout, headers, downloadExecutor); 33 34 } 34 35 -
/trunk/src/org/openstreetmap/josm/data/imagery/WMTSTileSource.java
r13733 r13732 51 51 import org.openstreetmap.josm.data.coor.EastNorth; 52 52 import org.openstreetmap.josm.data.coor.LatLon; 53 import org.openstreetmap.josm.data.imagery.GetCapabilitiesParseHelper.TransferMode;54 import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType;55 53 import org.openstreetmap.josm.data.projection.Projection; 56 54 import org.openstreetmap.josm.data.projection.Projections; … … 128 126 } 129 127 130 /** 131 * 132 * class representing WMTS TileMatrixSet 133 * This connects projection and TileMatrix (how the map is divided in tiles) 134 * 135 */ 136 public static class TileMatrixSet { 128 private static class TileMatrixSet { 137 129 138 130 private final List<TileMatrix> tileMatrix; … … 162 154 return "TileMatrixSet [crs=" + crs + ", identifier=" + identifier + ']'; 163 155 } 164 165 /**166 *167 * @return identifier of this TileMatrixSet168 */169 public String getIdentifier() {170 return identifier;171 }172 156 } 173 157 … … 178 162 } 179 163 180 /** 181 * Class representing WMTS Layer information 182 * 183 */ 184 public static class Layer { 164 private static class Layer { 185 165 private String format; 186 166 private String identifier; … … 222 202 + tileMatrixSet + ", baseUrl=" + baseUrl + ", style=" + style + ']'; 223 203 } 224 225 /**226 *227 * @return identifier of this layer228 */229 public String getIdentifier() {230 return identifier;231 }232 233 /**234 *235 * @return style of this layer236 */237 public String getStyle() {238 return style;239 }240 241 /**242 *243 * @return244 */245 public TileMatrixSet getTileMatrixSet() {246 return tileMatrixSet;247 }248 }249 250 /**251 * Exception thrown when praser doesn't find expected information in GetCapabilities document252 *253 */254 public static class WMTSGetCapabilitiesException extends Exception {255 256 /**257 * @param cause description of cause258 */259 public WMTSGetCapabilitiesException(String cause) {260 super(cause);261 }262 204 } 263 205 … … 269 211 super(Main.parent, tr("Select WMTS layer"), tr("Add layers"), tr("Cancel")); 270 212 this.layers = groupLayersByNameAndTileMatrixSet(layers); 271 this.list = getLayerSelectionPanel(this.layers); 213 //getLayersTable(layers, Main.getProjection()) 214 this.list = new JTable( 215 new AbstractTableModel() { 216 @Override 217 public Object getValueAt(int rowIndex, int columnIndex) { 218 switch (columnIndex) { 219 case 0: 220 return SelectLayerDialog.this.layers.get(rowIndex).getValue() 221 .stream() 222 .map(Layer::getUserTitle) 223 .collect(Collectors.joining(", ")); //this should be only one 224 case 1: 225 return SelectLayerDialog.this.layers.get(rowIndex).getValue() 226 .stream() 227 .map(x -> x.tileMatrixSet.crs) 228 .collect(Collectors.joining(", ")); 229 case 2: 230 return SelectLayerDialog.this.layers.get(rowIndex).getValue() 231 .stream() 232 .map(x -> x.tileMatrixSet.identifier) 233 .collect(Collectors.joining(", ")); //this should be only one 234 default: 235 throw new IllegalArgumentException(); 236 } 237 } 238 239 @Override 240 public int getRowCount() { 241 return SelectLayerDialog.this.layers.size(); 242 } 243 244 @Override 245 public int getColumnCount() { 246 return 3; 247 } 248 249 @Override 250 public String getColumnName(int column) { 251 switch (column) { 252 case 0: return tr("Layer name"); 253 case 1: return tr("Projection"); 254 case 2: return tr("Matrix set identifier"); 255 default: 256 throw new IllegalArgumentException(); 257 } 258 } 259 }); 260 this.list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 261 this.list.setAutoCreateRowSorter(true); 262 this.list.setRowSelectionAllowed(true); 263 this.list.setColumnSelectionAllowed(false); 272 264 JPanel panel = new JPanel(new GridBagLayout()); 273 265 panel.add(new JScrollPane(this.list), GBC.eol().fill()); … … 281 273 } 282 274 Layer selectedLayer = layers.get(list.convertRowIndexToModel(index)).getValue().get(0); 283 return new DefaultLayer(ImageryType.WMTS, selectedLayer.identifier, selectedLayer.style, selectedLayer.tileMatrixSet.identifier); 284 } 285 275 return new WMTSDefaultLayer(selectedLayer.identifier, selectedLayer.tileMatrixSet.identifier); 276 } 277 278 private static List<Entry<String, List<Layer>>> groupLayersByNameAndTileMatrixSet(Collection<Layer> layers) { 279 Map<String, List<Layer>> layerByName = layers.stream().collect( 280 Collectors.groupingBy(x -> x.identifier + '\u001c' + x.tileMatrixSet.identifier)); 281 return layerByName.entrySet().stream().sorted(Map.Entry.comparingByKey()).collect(Collectors.toList()); 282 } 286 283 } 287 284 … … 295 292 private ScaleList nativeScaleList; 296 293 297 private final DefaultLayer defaultLayer; 294 private final WMTSDefaultLayer defaultLayer; 298 295 299 296 private Projection tileProjection; … … 303 300 * @param info imagery info 304 301 * @throws IOException if any I/O error occurs 305 * @throws WMTSGetCapabilitiesException306 302 * @throws IllegalArgumentException if any other error happens for the given imagery info 307 303 */ 308 public WMTSTileSource(ImageryInfo info) throws IOException , WMTSGetCapabilitiesException{304 public WMTSTileSource(ImageryInfo info) throws IOException { 309 305 super(info); 310 306 CheckParameterUtil.ensureThat(info.getDefaultLayers().size() < 2, "At most 1 default layer for WMTS is supported"); 311 this.headers.putAll(info.getCustomHttpHeaders()); 307 312 308 this.baseUrl = GetCapabilitiesParseHelper.normalizeCapabilitiesUrl(handleTemplate(info.getUrl())); 313 WMTSCapabilities capabilities = getCapabilities(baseUrl, headers); 314 this.layers = capabilities.getLayers(); 315 this.baseUrl = capabilities.getBaseUrl(); 316 this.transferMode = capabilities.getTransferMode(); 309 this.layers = getCapabilities(); 317 310 if (info.getDefaultLayers().isEmpty()) { 318 311 Logging.warn(tr("No default layer selected, choosing first layer.")); 319 312 if (!layers.isEmpty()) { 320 313 Layer first = layers.iterator().next(); 321 this.defaultLayer = new DefaultLayer( info.getImageryType(),first.identifier, first.style, first.tileMatrixSet.identifier);314 this.defaultLayer = new WMTSDefaultLayer(first.identifier, first.tileMatrixSet.identifier); 322 315 } else { 323 316 this.defaultLayer = null; 324 317 } 325 318 } else { 326 this.defaultLayer = info.getDefaultLayers().iterator().next(); 319 DefaultLayer defLayer = info.getDefaultLayers().iterator().next(); 320 if (defLayer instanceof WMTSDefaultLayer) { 321 this.defaultLayer = (WMTSDefaultLayer) defLayer; 322 } else { 323 this.defaultLayer = null; 324 } 327 325 } 328 326 if (this.layers.isEmpty()) … … 345 343 // only one tile matrix set with matching projection - no point in asking 346 344 Layer selectedLayer = ls.get(0); 347 return new DefaultLayer( ImageryType.WMTS,selectedLayer.identifier, selectedLayer.style, selectedLayer.tileMatrixSet.identifier);345 return new WMTSDefaultLayer(selectedLayer.identifier, selectedLayer.tileMatrixSet.identifier); 348 346 } 349 347 } … … 368 366 } 369 367 370 371 /** 372 * @param url of the getCapabilities document 373 * @param headers HTTP headers to set when calling getCapabilities url 368 /** 374 369 * @return capabilities 375 370 * @throws IOException in case of any I/O error 376 * @throws WMTSGetCapabilitiesException377 371 * @throws IllegalArgumentException in case of any other error 378 372 */ 379 p ublic static WMTSCapabilitiesgetCapabilities(String url, Map<String, String> headers) throws IOException, WMTSGetCapabilitiesException {380 try (CachedFile cf = new CachedFile( url); InputStream in = cf.setHttpHeaders(headers).373 private Collection<Layer> getCapabilities() throws IOException { 374 try (CachedFile cf = new CachedFile(baseUrl); InputStream in = cf.setHttpHeaders(headers). 381 375 setMaxAge(Config.getPref().getLong("wmts.capabilities.cache.max_age", 7 * CachedFile.DAYS)). 382 376 setCachingStrategy(CachedFile.CachingStrategy.IfModifiedSince). … … 385 379 if (data.length == 0) { 386 380 cf.clear(); 387 throw new IllegalArgumentException("Could not read data from: " + url);381 throw new IllegalArgumentException("Could not read data from: " + baseUrl); 388 382 } 389 383 390 384 try { 391 385 XMLStreamReader reader = GetCapabilitiesParseHelper.getReader(new ByteArrayInputStream(data)); 392 WMTSCapabilities ret = null; 393 Collection<Layer> layers = null; 386 Collection<Layer> ret = new ArrayList<>(); 394 387 for (int event = reader.getEventType(); reader.hasNext(); event = reader.next()) { 395 388 if (event == XMLStreamReader.START_ELEMENT) { 396 389 if (GetCapabilitiesParseHelper.QN_OWS_OPERATIONS_METADATA.equals(reader.getName())) { 397 ret =parseOperationMetadata(reader);390 parseOperationMetadata(reader); 398 391 } 399 392 400 393 if (QN_CONTENTS.equals(reader.getName())) { 401 layers= parseContents(reader);394 ret = parseContents(reader); 402 395 } 403 396 } 404 397 } 405 if (ret == null) {406 /*407 * see #12168 - create dummy operation metadata - not all WMTS services provide this information408 *409 * WMTS Standard:410 * > Resource oriented architecture style HTTP encodings SHALL not be described in the OperationsMetadata section.411 *412 * And OperationMetada is not mandatory element. So REST mode is justifiable413 */414 ret = new WMTSCapabilities(url, TransferMode.REST);415 }416 if (layers == null) {417 throw new WMTSGetCapabilitiesException(tr("WMTS Capabilties document did not contain layers in url: {0}", url));418 }419 ret.addLayers(layers);420 398 return ret; 421 399 } catch (XMLStreamException e) { … … 478 456 supportedMimeTypes.add("image/jpgpng"); // used by ESRI 479 457 supportedMimeTypes.add("image/png8"); // used by geoserver 480 if (supportedMimeTypes.contains("image/jpeg")) {481 supportedMimeTypes.add("image/jpg"); // sometimes mispelled by Arcgis482 }483 458 Collection<String> unsupportedFormats = new ArrayList<>(); 484 459 … … 664 639 /** 665 640 * Parses OperationMetadata section. Returns when reader is on OperationsMetadata closing tag. 666 * return WMTSCapabilities withbaseUrl and transferMode641 * Sets this.baseUrl and this.transferMode 667 642 * 668 643 * @param reader StAX reader instance 669 * @return WMTSCapabilities with baseUrl and transferMode set670 644 * @throws XMLStreamException See {@link XMLStreamReader} 671 645 */ 672 private static WMTSCapabilitiesparseOperationMetadata(XMLStreamReader reader) throws XMLStreamException {646 private void parseOperationMetadata(XMLStreamReader reader) throws XMLStreamException { 673 647 for (int event = reader.getEventType(); 674 648 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && … … 683 657 GetCapabilitiesParseHelper.QN_OWS_GET 684 658 )) { 685 return new WMTSCapabilities( 686 reader.getAttributeValue(GetCapabilitiesParseHelper.XLINK_NS_URL, "href"), 687 GetCapabilitiesParseHelper.getTransferMode(reader) 688 ); 689 } 690 } 691 return null; 659 this.baseUrl = reader.getAttributeValue(GetCapabilitiesParseHelper.XLINK_NS_URL, "href"); 660 this.transferMode = GetCapabilitiesParseHelper.getTransferMode(reader); 661 } 662 } 692 663 } 693 664 … … 700 671 return; 701 672 List<Layer> matchingLayers = layers.stream().filter( 702 l -> l.identifier.equals(defaultLayer. getLayerName()) && l.tileMatrixSet.crs.equals(proj.toCode()))673 l -> l.identifier.equals(defaultLayer.layerName) && l.tileMatrixSet.crs.equals(proj.toCode())) 703 674 .collect(Collectors.toList()); 704 675 if (matchingLayers.size() > 1) { … … 715 686 this.tileProjection = null; 716 687 for (Layer layer : layers) { 717 if (!layer.identifier.equals(defaultLayer. getLayerName())) {688 if (!layer.identifier.equals(defaultLayer.layerName)) { 718 689 continue; 719 690 } … … 951 922 } 952 923 953 public static JTable getLayerSelectionPanel(List<Entry<String, List<Layer>>> layers) {954 JTable list = new JTable(955 new AbstractTableModel() {956 @Override957 public Object getValueAt(int rowIndex, int columnIndex) {958 switch (columnIndex) {959 case 0:960 return layers.get(rowIndex).getValue()961 .stream()962 .map(Layer::getUserTitle)963 .collect(Collectors.joining(", ")); //this should be only one964 case 1:965 return layers.get(rowIndex).getValue()966 .stream()967 .map(x -> x.tileMatrixSet.crs)968 .collect(Collectors.joining(", "));969 case 2:970 return layers.get(rowIndex).getValue()971 .stream()972 .map(x -> x.tileMatrixSet.identifier)973 .collect(Collectors.joining(", ")); //this should be only one974 default:975 throw new IllegalArgumentException();976 }977 }978 979 @Override980 public int getRowCount() {981 return layers.size();982 }983 984 @Override985 public int getColumnCount() {986 return 3;987 }988 989 @Override990 public String getColumnName(int column) {991 switch (column) {992 case 0: return tr("Layer name");993 case 1: return tr("Projection");994 case 2: return tr("Matrix set identifier");995 default:996 throw new IllegalArgumentException();997 }998 }999 });1000 list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);1001 list.setAutoCreateRowSorter(true);1002 list.setRowSelectionAllowed(true);1003 list.setColumnSelectionAllowed(false);1004 return list;1005 }1006 1007 public static List<Entry<String, List<Layer>>> groupLayersByNameAndTileMatrixSet(Collection<Layer> layers) {1008 Map<String, List<Layer>> layerByName = layers.stream().collect(1009 Collectors.groupingBy(x -> x.identifier + '\u001c' + x.tileMatrixSet.identifier));1010 return layerByName.entrySet().stream().sorted(Map.Entry.comparingByKey()).collect(Collectors.toList());1011 }1012 1013 1014 924 /** 1015 925 * @return set of projection codes that this TileSource supports -
/trunk/src/org/openstreetmap/josm/gui/bbox/SlippyMapBBoxChooser.java
r13733 r13732 21 21 import java.util.Set; 22 22 import java.util.concurrent.CopyOnWriteArrayList; 23 import java.util.concurrent.TimeUnit;24 23 25 24 import javax.swing.ButtonModel; … … 159 158 TileLoaderFactory cachedLoaderFactory = AbstractCachedTileSourceLayer.getTileLoaderFactory("TMS", TMSCachedTileLoader.class); 160 159 if (cachedLoaderFactory != null) { 161 cachedLoader = cachedLoaderFactory.makeTileLoader(this, headers , TimeUnit.HOURS.toSeconds(1));160 cachedLoader = cachedLoaderFactory.makeTileLoader(this, headers); 162 161 } else { 163 162 cachedLoader = null; -
/trunk/src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java
r13733 r13732 203 203 // prepared to be moved to the painter 204 204 protected TileCoordinateConverter coordinateConverter; 205 private final long minimumTileExpire;206 205 207 206 /** … … 215 214 getFilterSettings().addFilterChangeListener(this); 216 215 getDisplaySettings().addSettingsChangeListener(this); 217 this.minimumTileExpire = info.getMinimumTileExpire();218 216 } 219 217 … … 276 274 Map<String, String> headers = getHeaders(tileSource); 277 275 278 tileLoader = getTileLoaderFactory().makeTileLoader(this, headers , minimumTileExpire);276 tileLoader = getTileLoaderFactory().makeTileLoader(this, headers); 279 277 280 278 try { … … 1761 1759 public PrecacheTask(ProgressMonitor progressMonitor) { 1762 1760 this.progressMonitor = progressMonitor; 1763 this.tileLoader = getTileLoaderFactory().makeTileLoader(this, getHeaders(tileSource) , minimumTileExpire);1761 this.tileLoader = getTileLoaderFactory().makeTileLoader(this, getHeaders(tileSource)); 1764 1762 if (this.tileLoader instanceof TMSCachedTileLoader) { 1765 1763 ((TMSCachedTileLoader) this.tileLoader).setDownloadExecutor( -
/trunk/src/org/openstreetmap/josm/gui/layer/ImageryLayer.java
r13733 r13732 196 196 switch(info.getImageryType()) { 197 197 case WMS: 198 case WMS_ENDPOINT:199 198 return new WMSLayer(info); 200 199 case WMTS: -
/trunk/src/org/openstreetmap/josm/gui/layer/WMSLayer.java
r13733 r13732 25 25 import org.openstreetmap.josm.data.imagery.TemplatedWMSTileSource; 26 26 import org.openstreetmap.josm.data.imagery.WMSCachedTileLoader; 27 import org.openstreetmap.josm.data.imagery.WMSEndpointTileSource;28 27 import org.openstreetmap.josm.data.preferences.BooleanProperty; 29 28 import org.openstreetmap.josm.data.preferences.IntegerProperty; … … 58 57 private static final String CACHE_REGION_NAME = "WMS"; 59 58 60 private List<String> serverProjections; 59 private final List<String> serverProjections; 61 60 62 61 /** … … 66 65 public WMSLayer(ImageryInfo info) { 67 66 super(info); 68 CheckParameterUtil.ensureThat(info.getImageryType() == ImageryType.WMS || info.getImageryType() == ImageryType.WMS_ENDPOINT, "ImageryType is WMS");67 CheckParameterUtil.ensureThat(info.getImageryType() == ImageryType.WMS, "ImageryType is WMS"); 69 68 CheckParameterUtil.ensureParameterNotNull(info.getUrl(), "info.url"); 70 if (info.getImageryType() == ImageryType.WMS) { 71 TemplatedWMSTileSource.checkUrl(info.getUrl()); 72 73 } 69 TemplatedWMSTileSource.checkUrl(info.getUrl()); 74 70 this.serverProjections = new ArrayList<>(info.getServerProjections()); 75 71 } … … 93 89 @Override 94 90 protected AbstractWMSTileSource getTileSource() { 95 AbstractWMSTileSource tileSource; 96 if (info.getImageryType() == ImageryType.WMS) { 97 tileSource = new TemplatedWMSTileSource(info, chooseProjection(Main.getProjection())); 98 } else { 99 /* 100 * Chicken-and-egg problem. We want to create tile source, but supported projections we can get only 101 * from this tile source. So create tilesource first with dummy Main.getProjection(), and then update 102 * once we update server projections. 103 * 104 * Thus: 105 * * it is not required to provide projections for wms_endpoint imagery types 106 * * we always use current definitions returned by server 107 */ 108 WMSEndpointTileSource endpointTileSource = new WMSEndpointTileSource(info, Main.getProjection()); 109 this.serverProjections = endpointTileSource.getServerProjections(); 110 endpointTileSource.setTileProjection(chooseProjection(Main.getProjection())); 111 tileSource = endpointTileSource; 112 } 91 AbstractWMSTileSource tileSource = new TemplatedWMSTileSource( 92 info, chooseProjection(Main.getProjection())); 113 93 info.setAttribution(tileSource); 114 94 return tileSource; -
/trunk/src/org/openstreetmap/josm/gui/layer/WMTSLayer.java
r13733 r13732 13 13 import org.openstreetmap.josm.data.imagery.WMSCachedTileLoader; 14 14 import org.openstreetmap.josm.data.imagery.WMTSTileSource; 15 import org.openstreetmap.josm.data.imagery.WMTSTileSource.WMTSGetCapabilitiesException;16 15 import org.openstreetmap.josm.data.projection.Projection; 17 16 import org.openstreetmap.josm.gui.MainApplication; … … 65 64 } 66 65 return null; 67 } catch (IOException | WMTSGetCapabilitiesExceptione) {66 } catch (IOException e) { 68 67 Logging.warn(e); 69 68 throw new IllegalArgumentException(e); -
/trunk/src/org/openstreetmap/josm/io/imagery/ImageryReader.java
r13733 r13732 7 7 import java.util.ArrayList; 8 8 import java.util.Arrays; 9 import java.util.HashMap; 9 10 import java.util.List; 10 11 import java.util.Map; 11 12 import java.util.Objects; 12 13 import java.util.Stack; 13 import java.util.concurrent.ConcurrentHashMap;14 14 15 15 import javax.xml.parsers.ParserConfigurationException; 16 16 17 import org.openstreetmap.josm.data.imagery.DefaultLayer;18 17 import org.openstreetmap.josm.data.imagery.ImageryInfo; 19 18 import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryBounds; … … 58 57 NO_TILESUM, 59 58 METADATA, 60 DEFAULT_LAYERS, 61 CUSTOM_HTTP_HEADERS, 62 NOOP, 63 UNKNOWN, // element is not recognized in the current context 59 UNKNOWN, // element is not recognized in the current context 64 60 } 65 61 … … 136 132 private MultiMap<String, String> noTileChecksums; 137 133 private Map<String, String> metadataHeaders; 138 private List<DefaultLayer> defaultLayers;139 private Map<String, String> customHttpHeaders;140 134 141 135 @Override … … 151 145 noTileHeaders = null; 152 146 noTileChecksums = null; 153 customHttpHeaders = null;154 147 } 155 148 … … 171 164 noTileHeaders = new MultiMap<>(); 172 165 noTileChecksums = new MultiMap<>(); 173 metadataHeaders = new ConcurrentHashMap<>(); 174 defaultLayers = new ArrayList<>(); 175 customHttpHeaders = new ConcurrentHashMap<>(); 166 metadataHeaders = new HashMap<>(); 176 167 String best = atts.getValue("eli-best"); 177 168 if (TRUE.equals(best)) { … … 224 215 TILE_SIZE, 225 216 "valid-georeference", 226 "mod-tile-features", 227 "transparent", 228 "minimum-tile-expire" 217 "mod-tile-features" 229 218 ).contains(qName)) { 230 219 newState = State.ENTRY_ATTRIBUTE; … … 258 247 metadataHeaders.put(atts.getValue("header-name"), atts.getValue("metadata-key")); 259 248 newState = State.METADATA; 260 } else if ("defaultLayers".equals(qName)) {261 newState = State.DEFAULT_LAYERS;262 } else if ("custom-http-header".equals(qName)) {263 customHttpHeaders.put(atts.getValue("header-name"), atts.getValue("header-value"));264 newState = State.CUSTOM_HTTP_HEADERS;265 249 } 266 250 break; … … 285 269 if ("code".equals(qName)) { 286 270 newState = State.CODE; 287 }288 break;289 case DEFAULT_LAYERS:290 if ("layer".equals(qName)) {291 newState = State.NOOP;292 defaultLayers.add(new DefaultLayer(entry.getImageryType(), atts.getValue("name"),atts.getValue("style"), atts.getValue("tileMatrixSet")));293 271 } 294 272 break; … … 328 306 entry.setMetadataHeaders(metadataHeaders); 329 307 metadataHeaders = null; 330 entry.setDefaultLayers(defaultLayers);331 defaultLayers = null;332 entry.setCustomHttpHeaders(customHttpHeaders);333 customHttpHeaders = null;334 308 335 309 if (!skipEntry) { … … 349 323 switch(qName) { 350 324 case "type": 351 ImageryType.values();352 325 boolean found = false; 353 326 for (ImageryType type : ImageryType.values()) { … … 514 487 case "mod-tile-features": 515 488 entry.setModTileFeatures(Boolean.parseBoolean(accumulator.toString())); 516 break;517 case "transparent":518 entry.setTransparent(Boolean.parseBoolean(accumulator.toString()));519 break;520 case "minimum-tile-expire":521 entry.setMinimumTileExpire(Integer.valueOf(accumulator.toString()));522 489 break; 523 490 default: // Do nothing -
/trunk/src/org/openstreetmap/josm/io/imagery/WMSImagery.java
r13733 r13732 2 2 package org.openstreetmap.josm.io.imagery; 3 3 4 import static java.nio.charset.StandardCharsets.UTF_8; 5 import static org.openstreetmap.josm.tools.I18n.tr; 6 7 import java.io.File; 4 import java.awt.HeadlessException; 8 5 import java.io.IOException; 9 6 import java.io.InputStream; 7 import java.io.StringReader; 8 import java.io.StringWriter; 10 9 import java.net.MalformedURLException; 11 10 import java.net.URL; … … 14 13 import java.util.Collections; 15 14 import java.util.HashSet; 15 import java.util.Iterator; 16 16 import java.util.List; 17 import java.util.Map; 17 import java.util.Locale; 18 import java.util.NoSuchElementException; 18 19 import java.util.Set; 19 import java.util.concurrent.ConcurrentHashMap; 20 import java.util.function.Function; 20 import java.util.regex.Matcher; 21 21 import java.util.regex.Pattern; 22 22 import java.util.stream.Collectors; 23 import java.util.stream.Stream; 24 import java.util.stream.StreamSupport; 23 25 24 26 import javax.imageio.ImageIO; 25 import javax.xml.namespace.QName; 26 import javax.xml.stream.XMLStreamException; 27 import javax.xml.stream.XMLStreamReader; 27 import javax.xml.parsers.DocumentBuilder; 28 import javax.xml.parsers.ParserConfigurationException; 29 import javax.xml.transform.TransformerException; 30 import javax.xml.transform.TransformerFactory; 31 import javax.xml.transform.TransformerFactoryConfigurationError; 32 import javax.xml.transform.dom.DOMSource; 33 import javax.xml.transform.stream.StreamResult; 28 34 29 35 import org.openstreetmap.josm.data.Bounds; 30 import org.openstreetmap.josm.data.coor.EastNorth;31 import org.openstreetmap.josm.data.imagery.DefaultLayer;32 36 import org.openstreetmap.josm.data.imagery.GetCapabilitiesParseHelper; 33 37 import org.openstreetmap.josm.data.imagery.ImageryInfo; 34 import org.openstreetmap.josm.data.imagery.LayerDetails;35 import org.openstreetmap.josm.data.projection.Projection;36 38 import org.openstreetmap.josm.data.projection.Projections; 37 import org.openstreetmap.josm.io.CachedFile; 39 import org.openstreetmap.josm.tools.HttpClient; 40 import org.openstreetmap.josm.tools.HttpClient.Response; 38 41 import org.openstreetmap.josm.tools.Logging; 39 42 import org.openstreetmap.josm.tools.Utils; 43 import org.w3c.dom.Document; 44 import org.w3c.dom.Element; 45 import org.w3c.dom.Node; 46 import org.w3c.dom.NodeList; 47 import org.xml.sax.InputSource; 48 import org.xml.sax.SAXException; 40 49 41 50 /** … … 44 53 public class WMSImagery { 45 54 46 47 private static final String CAPABILITIES_QUERY_STRING = "SERVICE=WMS&REQUEST=GetCapabilities"; 48 49 /** 50 * WMS namespace address 51 */ 52 public static final String WMS_NS_URL = "http://www.opengis.net/wms"; 53 54 // CHECKSTYLE.OFF: SingleSpaceSeparator 55 // WMS 1.0 - 1.3.0 56 private static final QName CAPABILITITES_ROOT_130 = new QName("WMS_Capabilities", WMS_NS_URL); 57 private static final QName QN_ABSTRACT = new QName(WMS_NS_URL, "Abstract"); 58 private static final QName QN_CAPABILITY = new QName(WMS_NS_URL, "Capability"); 59 private static final QName QN_CRS = new QName(WMS_NS_URL, "CRS"); 60 private static final QName QN_DCPTYPE = new QName(WMS_NS_URL, "DCPType"); 61 private static final QName QN_FORMAT = new QName(WMS_NS_URL, "Format"); 62 private static final QName QN_GET = new QName(WMS_NS_URL, "Get"); 63 private static final QName QN_GETMAP = new QName(WMS_NS_URL, "GetMap"); 64 private static final QName QN_HTTP = new QName(WMS_NS_URL, "HTTP"); 65 private static final QName QN_LAYER = new QName(WMS_NS_URL, "Layer"); 66 private static final QName QN_NAME = new QName(WMS_NS_URL, "Name"); 67 private static final QName QN_REQUEST = new QName(WMS_NS_URL, "Request"); 68 private static final QName QN_SERVICE = new QName(WMS_NS_URL, "Service"); 69 private static final QName QN_STYLE = new QName(WMS_NS_URL, "Style"); 70 private static final QName QN_TITLE = new QName(WMS_NS_URL, "Title"); 71 private static final QName QN_BOUNDINGBOX = new QName(WMS_NS_URL, "BoundingBox"); 72 private static final QName QN_EX_GEOGRAPHIC_BBOX = new QName(WMS_NS_URL, "EX_GeographicBoundingBox"); 73 private static final QName QN_WESTBOUNDLONGITUDE = new QName(WMS_NS_URL, "westBoundLongitude"); 74 private static final QName QN_EASTBOUNDLONGITUDE = new QName(WMS_NS_URL, "eastBoundLongitude"); 75 private static final QName QN_SOUTHBOUNDLATITUDE = new QName(WMS_NS_URL, "southBoundLatitude"); 76 private static final QName QN_NORTHBOUNDLATITUDE = new QName(WMS_NS_URL, "northBoundLatitude"); 77 private static final QName QN_ONLINE_RESOURCE = new QName(WMS_NS_URL, "OnlineResource"); 78 79 // WMS 1.1 - 1.1.1 80 private static final QName CAPABILITIES_ROOT_111 = new QName("WMT_MS_Capabilities"); 81 private static final QName QN_SRS = new QName("SRS"); 82 private static final QName QN_LATLONBOUNDINGBOX = new QName("LatLonBoundingBox"); 83 84 // CHECKSTYLE.ON: SingleSpaceSeparator 55 private static final class ChildIterator implements Iterator<Element> { 56 private Element child; 57 58 ChildIterator(Element parent) { 59 child = advanceToElement(parent.getFirstChild()); 60 } 61 62 private static Element advanceToElement(Node firstChild) { 63 Node node = firstChild; 64 while (node != null && !(node instanceof Element)) { 65 node = node.getNextSibling(); 66 } 67 return (Element) node; 68 } 69 70 @Override 71 public boolean hasNext() { 72 return child != null; 73 } 74 75 @Override 76 public Element next() { 77 if (!hasNext()) { 78 throw new NoSuchElementException("No next sibling."); 79 } 80 Element next = child; 81 child = advanceToElement(child.getNextSibling()); 82 return next; 83 } 84 } 85 85 86 86 /** … … 120 120 } 121 121 122 private Map<String, String> headers = new ConcurrentHashMap<>(); 123 private String version = "1.1.1"; // default version 124 private String getMapUrl; 125 private URL capabilitiesUrl; 126 private List<String> formats = new ArrayList<>(); 127 private List<LayerDetails> layers = new ArrayList<>(); 128 129 private String title; 130 131 /** 132 * Make getCapabilities request towards given URL 133 * @param url service url 134 * @throws IOException 135 * @throws WMSGetCapabilitiesException 136 */ 137 public WMSImagery(String url) throws IOException, WMSGetCapabilitiesException { 138 this(url, null); 139 } 140 141 /** 142 * Make getCapabilities request towards given URL using headers 143 * @param url service url 144 * @param headers HTTP headers to be sent with request 145 * @throws IOException 146 * @throws WMSGetCapabilitiesException 147 */ 148 public WMSImagery(String url, Map<String, String> headers) throws IOException, WMSGetCapabilitiesException { 149 if (headers != null) { 150 this.headers.putAll(headers); 151 } 152 153 IOException savedExc = null; 154 String workingAddress = null; 155 url_search: 156 for (String z: new String[]{ 157 normalizeUrl(url), 158 url, 159 url + CAPABILITIES_QUERY_STRING, 160 }) { 161 for (String ver: new String[]{"", "&VERSION=1.3.0", "&VERSION=1.1.1"}) { 162 try { 163 attemptGetCapabilities(z + ver); 164 workingAddress = z; 165 calculateChildren(); 166 // clear saved exception - we've got something working 167 savedExc = null; 168 break url_search; 169 } catch (IOException e) { 170 savedExc = e; 171 Logging.warn(e); 172 } 173 } 174 } 175 176 if (workingAddress != null) { 177 try { 178 capabilitiesUrl = new URL(workingAddress); 179 } catch (MalformedURLException e) { 180 if (savedExc != null) { 181 savedExc = e; 182 } 183 try { 184 capabilitiesUrl = new File(workingAddress).toURI().toURL(); 185 } catch (MalformedURLException e1) { 186 // do nothing, raise original exception 187 } 188 } 189 } 190 191 if (savedExc != null) { 192 throw savedExc; 193 } 194 } 195 196 private void calculateChildren() { 197 Map<LayerDetails, List<LayerDetails>> layerChildren = layers.stream() 198 .filter(x -> x.getParent() != null) // exclude top-level elements 199 .collect(Collectors.groupingBy(LayerDetails::getParent)); 200 for (LayerDetails ld: layers) { 201 if (layerChildren.containsKey(ld)) { 202 ld.setChildren(layerChildren.get(ld)); 203 } 204 } 205 // leave only top-most elements in the list 206 layers = layers.stream().filter(x -> x.getParent() == null).collect(Collectors.toCollection(ArrayList::new)); 207 } 208 209 /** 210 * Returns the list of top-level layers. 211 * @return the list of top-level layers 122 private List<LayerDetails> layers; 123 private URL serviceUrl; 124 private List<String> formats; 125 private String version = "1.1.1"; 126 127 /** 128 * Returns the list of layers. 129 * @return the list of layers 212 130 */ 213 131 public List<LayerDetails> getLayers() { … … 216 134 217 135 /** 136 * Returns the service URL. 137 * @return the service URL 138 */ 139 public URL getServiceUrl() { 140 return serviceUrl; 141 } 142 143 /** 144 * Returns the WMS version used. 145 * @return the WMS version used (1.1.1 or 1.3.0) 146 * @since 13358 147 */ 148 public String getVersion() { 149 return version; 150 } 151 152 /** 218 153 * Returns the list of supported formats. 219 154 * @return the list of supported formats 220 155 */ 221 public Collection<String> getFormats() {156 public List<String> getFormats() { 222 157 return Collections.unmodifiableList(formats); 223 158 } 224 159 225 160 /** 226 * Gets the preferred format for this imagery layer. 227 * @return The preferred format as mime type. 228 */ 229 public String getPreferredFormat() { 230 if (formats.contains("image/png")) { 161 * Gets the preffered format for this imagery layer. 162 * @return The preffered format as mime type. 163 */ 164 public String getPreferredFormats() { 165 if (formats.contains("image/jpeg")) { 166 return "image/jpeg"; 167 } else if (formats.contains("image/png")) { 231 168 return "image/png"; 232 } else if (formats.contains("image/jpeg")) {233 return "image/jpeg";234 169 } else if (formats.isEmpty()) { 235 170 return null; … … 239 174 } 240 175 241 /** 242 * @return root URL of services in this GetCapabilities 243 */ 244 public String buildRootUrl() { 245 if (getMapUrl == null && capabilitiesUrl == null) { 176 String buildRootUrl() { 177 if (serviceUrl == null) { 246 178 return null; 247 179 } 248 if (getMapUrl != null) {249 return getMapUrl;250 }251 252 URL serviceUrl = capabilitiesUrl;253 180 StringBuilder a = new StringBuilder(serviceUrl.getProtocol()); 254 181 a.append("://").append(serviceUrl.getHost()); … … 267 194 268 195 /** 269 * Returns URL for accessing GetMap service. String will contain following parameters: 270 * * {proj} - that needs to be replaced with projection (one of {@link #getServerProjections(List)}) 271 * * {width} - that needs to be replaced with width of the tile 272 * * {height} - that needs to be replaces with height of the tile 273 * * {bbox} - that needs to be replaced with area that should be fetched (in {proj} coordinates) 274 * 275 * Format of the response will be calculated using {@link #getPreferredFormat()} 276 * 277 * @param selectedLayers list of DefaultLayer selection of layers to be shown 278 * @param transparent whether returned images should contain transparent pixels (if supported by format) 279 * @return URL template for GetMap service containing 280 */ 281 public String buildGetMapUrl(List<DefaultLayer> selectedLayers, boolean transparent) { 282 return buildGetMapUrl( 283 getLayers(selectedLayers), 284 selectedLayers.stream().map(x -> x.getStyle()).collect(Collectors.toList()), 285 transparent); 286 } 287 288 /** 289 * @see #buildGetMapUrl(List, boolean) 290 * 291 * @param selectedLayers selected layers as subset of the tree returned by {@link #getLayers()} 292 * @param selectedStyles selected styles for all selectedLayers 293 * @param transparent whether returned images should contain transparent pixels (if supported by format) 294 * @return URL template for GetMap service 295 */ 296 public String buildGetMapUrl(List<LayerDetails> selectedLayers, List<String> selectedStyles, boolean transparent) { 297 return buildGetMapUrl( 298 selectedLayers.stream().map(x -> x.getName()).collect(Collectors.toList()), 299 selectedStyles, 300 getPreferredFormat(), 301 transparent); 302 } 303 304 /** 305 * @see #buildGetMapUrl(List, boolean) 306 * 307 * @param selectedLayers selected layers as list of strings 308 * @param selectedStyles selected styles of layers as list of strings 309 * @param format format of the response - one of {@link #getFormats()} 310 * @param transparent whether returned images should contain transparent pixels (if supported by format) 311 * @return URL template for GetMap service 312 */ 313 public String buildGetMapUrl(List<String> selectedLayers, 314 Collection<String> selectedStyles, 315 String format, 316 boolean transparent) { 317 318 Utils.ensure(selectedStyles == null || selectedLayers.size() == selectedStyles.size(), 319 tr("Styles size {0} doesn't match layers size {1}"), 320 selectedStyles == null ? 0 : selectedStyles.size(), 321 selectedLayers.size()); 322 323 return buildRootUrl() + "FORMAT=" + format + ((imageFormatHasTransparency(format) && transparent) ? "&TRANSPARENT=TRUE" : "") 324 + "&VERSION=" + this.version + "&SERVICE=WMS&REQUEST=GetMap&LAYERS=" 325 + selectedLayers.stream().collect(Collectors.joining(",")) 326 + "&STYLES=" 327 + (selectedStyles != null ? Utils.join(",", selectedStyles) : "") 328 + "&" 329 + (belowWMS130() ? "SRS" : "CRS") 330 + "={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}"; 331 } 332 333 private boolean tagEquals(QName a, QName b) { 334 boolean ret = a.equals(b); 335 if (ret) { 336 return ret; 337 } 338 339 if (belowWMS130()) { 340 return a.getLocalPart().equals(b.getLocalPart()); 341 } 342 343 return false; 344 } 345 346 private void attemptGetCapabilities(String url) throws IOException, WMSGetCapabilitiesException { 347 Logging.debug("Trying WMS getcapabilities with url {0}", url); 348 try (CachedFile cf = new CachedFile(url); InputStream in = cf.setHttpHeaders(headers). 349 setMaxAge(7 * CachedFile.DAYS). 350 setCachingStrategy(CachedFile.CachingStrategy.IfModifiedSince). 351 getInputStream()) { 196 * Returns the URL for the "GetMap" WMS request in JPEG format. 197 * @param selectedLayers the list of selected layers, matching the "LAYERS" WMS request argument 198 * @return the URL for the "GetMap" WMS request 199 */ 200 public String buildGetMapUrl(Collection<LayerDetails> selectedLayers) { 201 return buildGetMapUrl(selectedLayers, "image/jpeg"); 202 } 203 204 /** 205 * Returns the URL for the "GetMap" WMS request. 206 * @param selectedLayers the list of selected layers, matching the "LAYERS" WMS request argument 207 * @param format the requested image format, matching the "FORMAT" WMS request argument 208 * @return the URL for the "GetMap" WMS request 209 */ 210 public String buildGetMapUrl(Collection<LayerDetails> selectedLayers, String format) { 211 return buildRootUrl() + "FORMAT=" + format + (imageFormatHasTransparency(format) ? "&TRANSPARENT=TRUE" : "") 212 + "&VERSION=" + version + "&SERVICE=WMS&REQUEST=GetMap&LAYERS=" 213 + selectedLayers.stream().map(x -> x.ident).collect(Collectors.joining(",")) 214 + "&STYLES=&" + ("1.3.0".equals(version) ? "CRS" : "SRS") + "={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}"; 215 } 216 217 /** 218 * Attempts WMS "GetCapabilities" request and initializes internal variables if successful. 219 * @param serviceUrlStr WMS service URL 220 * @throws IOException if any I/O errors occurs 221 * @throws WMSGetCapabilitiesException if the WMS server replies a ServiceException 222 */ 223 public void attemptGetCapabilities(String serviceUrlStr) throws IOException, WMSGetCapabilitiesException { 224 URL getCapabilitiesUrl = null; 225 try { 226 if (!Pattern.compile(".*GetCapabilities.*", Pattern.CASE_INSENSITIVE).matcher(serviceUrlStr).matches()) { 227 // If the url doesn't already have GetCapabilities, add it in 228 getCapabilitiesUrl = new URL(serviceUrlStr); 229 final String getCapabilitiesQuery = "VERSION=1.1.1&SERVICE=WMS&REQUEST=GetCapabilities"; 230 if (getCapabilitiesUrl.getQuery() == null) { 231 getCapabilitiesUrl = new URL(serviceUrlStr + '?' + getCapabilitiesQuery); 232 } else if (!getCapabilitiesUrl.getQuery().isEmpty() && !getCapabilitiesUrl.getQuery().endsWith("&")) { 233 getCapabilitiesUrl = new URL(serviceUrlStr + '&' + getCapabilitiesQuery); 234 } else { 235 getCapabilitiesUrl = new URL(serviceUrlStr + getCapabilitiesQuery); 236 } 237 } else { 238 // Otherwise assume it's a good URL and let the subsequent error 239 // handling systems deal with problems 240 getCapabilitiesUrl = new URL(serviceUrlStr); 241 } 242 // Make sure we don't keep GetCapabilities request in service URL 243 serviceUrl = new URL(serviceUrlStr.replace("REQUEST=GetCapabilities", "").replace("&&", "&")); 244 } catch (HeadlessException e) { 245 Logging.warn(e); 246 return; 247 } 248 249 doAttemptGetCapabilities(serviceUrlStr, getCapabilitiesUrl); 250 } 251 252 /** 253 * Attempts WMS GetCapabilities with version 1.1.1 first, then 1.3.0 in case of specific errors. 254 * @param serviceUrlStr WMS service URL 255 * @param getCapabilitiesUrl GetCapabilities URL 256 * @throws IOException if any I/O error occurs 257 * @throws WMSGetCapabilitiesException if any HTTP or parsing error occurs 258 */ 259 private void doAttemptGetCapabilities(String serviceUrlStr, URL getCapabilitiesUrl) 260 throws IOException, WMSGetCapabilitiesException { 261 final String url = getCapabilitiesUrl.toExternalForm(); 262 final Response response = HttpClient.create(getCapabilitiesUrl).connect(); 263 264 // Is the HTTP connection successul ? 265 if (response.getResponseCode() >= 400) { 266 // HTTP error for servers handling only WMS 1.3.0 ? 267 String errorMessage = response.getResponseMessage(); 268 String errorContent = response.fetchContent(); 269 Matcher tomcat = HttpClient.getTomcatErrorMatcher(errorContent); 270 boolean messageAbout130 = errorMessage != null && errorMessage.contains("1.3.0"); 271 boolean contentAbout130 = errorContent != null && tomcat != null && tomcat.matches() && tomcat.group(1).contains("1.3.0"); 272 if (url.contains("VERSION=1.1.1") && (messageAbout130 || contentAbout130)) { 273 doAttemptGetCapabilities130(serviceUrlStr, url); 274 return; 275 } 276 throw new WMSGetCapabilitiesException(errorMessage, errorContent); 277 } 278 279 try { 280 // Parse XML capabilities sent by the server 281 parseCapabilities(serviceUrlStr, response.getContent()); 282 } catch (WMSGetCapabilitiesException e) { 283 // ServiceException for servers handling only WMS 1.3.0 ? 284 if (e.getCause() == null && url.contains("VERSION=1.1.1")) { 285 doAttemptGetCapabilities130(serviceUrlStr, url); 286 } else { 287 throw e; 288 } 289 } 290 } 291 292 /** 293 * Attempts WMS GetCapabilities with version 1.3.0. 294 * @param serviceUrlStr WMS service URL 295 * @param url GetCapabilities URL 296 * @throws IOException if any I/O error occurs 297 * @throws WMSGetCapabilitiesException if any HTTP or parsing error occurs 298 * @throws MalformedURLException in case of invalid URL 299 */ 300 private void doAttemptGetCapabilities130(String serviceUrlStr, final String url) 301 throws IOException, WMSGetCapabilitiesException { 302 doAttemptGetCapabilities(serviceUrlStr, new URL(url.replace("VERSION=1.1.1", "VERSION=1.3.0"))); 303 if (serviceUrl.toExternalForm().contains("VERSION=1.1.1")) { 304 serviceUrl = new URL(serviceUrl.toExternalForm().replace("VERSION=1.1.1", "VERSION=1.3.0")); 305 } 306 version = "1.3.0"; 307 } 308 309 void parseCapabilities(String serviceUrlStr, InputStream contentStream) throws IOException, WMSGetCapabilitiesException { 310 String incomingData = null; 311 try { 312 DocumentBuilder builder = Utils.newSafeDOMBuilder(); 313 builder.setEntityResolver((publicId, systemId) -> { 314 Logging.info("Ignoring DTD " + publicId + ", " + systemId); 315 return new InputSource(new StringReader("")); 316 }); 317 Document document = builder.parse(contentStream); 318 Element root = document.getDocumentElement(); 352 319 353 320 try { 354 XMLStreamReader reader = GetCapabilitiesParseHelper.getReader(in); 355 for (int event = reader.getEventType(); reader.hasNext(); event = reader.next()) { 356 if (event == XMLStreamReader.START_ELEMENT) { 357 if (tagEquals(CAPABILITIES_ROOT_111, reader.getName())) { 358 // version 1.1.1 359 this.version = reader.getAttributeValue(null, "version"); 360 if (this.version == null) { 361 this.version = "1.1.1"; 362 } 363 } 364 if (tagEquals(CAPABILITITES_ROOT_130, reader.getName())) { 365 this.version = reader.getAttributeValue(WMS_NS_URL, "version"); 366 } 367 if (tagEquals(QN_SERVICE, reader.getName())) { 368 parseService(reader); 369 } 370 371 if (tagEquals(QN_CAPABILITY, reader.getName())) { 372 parseCapability(reader); 373 } 321 StringWriter writer = new StringWriter(); 322 TransformerFactory.newInstance().newTransformer().transform(new DOMSource(document), new StreamResult(writer)); 323 incomingData = writer.getBuffer().toString(); 324 Logging.debug("Server response to Capabilities request:"); 325 Logging.debug(incomingData); 326 } catch (TransformerFactoryConfigurationError | TransformerException e) { 327 Logging.warn(e); 328 } 329 330 // Check if the request resulted in ServiceException 331 if ("ServiceException".equals(root.getTagName())) { 332 throw new WMSGetCapabilitiesException(root.getTextContent(), incomingData); 333 } 334 335 // Some WMS service URLs specify a different base URL for their GetMap service 336 Element child = getChild(root, "Capability"); 337 child = getChild(child, "Request"); 338 child = getChild(child, "GetMap"); 339 340 formats = getChildrenStream(child, "Format") 341 .map(Node::getTextContent) 342 .filter(WMSImagery::isImageFormatSupportedWarn) 343 .collect(Collectors.toList()); 344 345 child = getChild(child, "DCPType"); 346 child = getChild(child, "HTTP"); 347 child = getChild(child, "Get"); 348 child = getChild(child, "OnlineResource"); 349 if (child != null) { 350 String baseURL = child.getAttributeNS(GetCapabilitiesParseHelper.XLINK_NS_URL, "href"); 351 if (!baseURL.equals(serviceUrlStr)) { 352 URL newURL = new URL(baseURL); 353 if (newURL.getAuthority() != null) { 354 Logging.info("GetCapabilities specifies a different service URL: " + baseURL); 355 serviceUrl = newURL; 374 356 } 375 357 } 376 } catch (XMLStreamException e) { 377 String content = new String(cf.getByteContent(), UTF_8); 378 cf.clear(); // if there is a problem with parsing of the file, remove it from the cache 379 throw new WMSGetCapabilitiesException(e, content); 380 } 381 } 382 } 383 384 private void parseService(XMLStreamReader reader) throws XMLStreamException { 385 if (GetCapabilitiesParseHelper.moveReaderToTag(reader, this::tagEquals, QN_TITLE)) { 386 this.title = reader.getElementText(); 387 for (int event = reader.getEventType(); 388 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && tagEquals(QN_SERVICE, reader.getName())); 389 event = reader.next()) { 390 // empty loop, just move reader to the end of Service tag, if moveReaderToTag return false, it's already done 391 } 392 } 393 } 394 395 private void parseCapability(XMLStreamReader reader) throws XMLStreamException { 396 for (int event = reader.getEventType(); 397 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && tagEquals(QN_CAPABILITY, reader.getName())); 398 event = reader.next()) { 399 400 if (event == XMLStreamReader.START_ELEMENT) { 401 if (tagEquals(QN_REQUEST, reader.getName())) { 402 parseRequest(reader); 403 } 404 if (tagEquals(QN_LAYER, reader.getName())) { 405 parseLayer(reader, null); 406 } 407 } 408 } 409 } 410 411 private void parseRequest(XMLStreamReader reader) throws XMLStreamException { 412 String mode = ""; 413 String getMapUrl = ""; 414 if (GetCapabilitiesParseHelper.moveReaderToTag(reader, this::tagEquals, QN_GETMAP)) { 415 for (int event = reader.getEventType(); 416 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && tagEquals(QN_GETMAP, reader.getName())); 417 event = reader.next()) { 418 419 if (event == XMLStreamReader.START_ELEMENT) { 420 if (tagEquals(QN_FORMAT, reader.getName())) { 421 String value = reader.getElementText(); 422 if (isImageFormatSupportedWarn(value) && !this.formats.contains(value)) { 423 this.formats.add(value); 424 } 425 } 426 if (tagEquals(QN_DCPTYPE, reader.getName()) && GetCapabilitiesParseHelper.moveReaderToTag(reader, 427 this::tagEquals, QN_HTTP, QN_GET)) { 428 mode = reader.getName().getLocalPart(); 429 if (GetCapabilitiesParseHelper.moveReaderToTag(reader, this::tagEquals, QN_ONLINE_RESOURCE)) { 430 getMapUrl = reader.getAttributeValue(GetCapabilitiesParseHelper.XLINK_NS_URL, "href"); 431 } 432 // TODO should we handle also POST? 433 if ("GET".equalsIgnoreCase(mode) && getMapUrl != null && !"".equals(getMapUrl)) { 434 this.getMapUrl = getMapUrl; 435 } 436 } 437 } 438 } 439 } 440 } 441 442 private void parseLayer(XMLStreamReader reader, LayerDetails parentLayer) throws XMLStreamException { 443 LayerDetails ret = new LayerDetails(parentLayer); 444 for (int event = reader.next(); // start with advancing reader by one element to get the contents of the layer 445 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && tagEquals(QN_LAYER, reader.getName())); 446 event = reader.next()) { 447 448 if (event == XMLStreamReader.START_ELEMENT) { 449 if (tagEquals(QN_NAME, reader.getName())) { 450 ret.setName(reader.getElementText()); 451 } 452 if (tagEquals(QN_ABSTRACT, reader.getName())) { 453 ret.setAbstract(GetCapabilitiesParseHelper.getElementTextWithSubtags(reader)); 454 } 455 if (tagEquals(QN_TITLE, reader.getName())) { 456 ret.setTitle(reader.getElementText()); 457 } 458 if (tagEquals(QN_CRS, reader.getName())) { 459 ret.addCrs(reader.getElementText()); 460 } 461 if (tagEquals(QN_SRS, reader.getName()) && belowWMS130()) { 462 ret.addCrs(reader.getElementText()); 463 } 464 if (tagEquals(QN_STYLE, reader.getName())) { 465 parseAndAddStyle(reader, ret); 466 } 467 if (tagEquals(QN_LAYER, reader.getName())) { 468 469 parseLayer(reader, ret); 470 } 471 if (tagEquals(QN_EX_GEOGRAPHIC_BBOX, reader.getName())) { 472 if (ret.getBounds() == null) { 473 Bounds bbox = parseExGeographic(reader); 474 ret.setBounds(bbox); 475 } 476 477 } 478 if (tagEquals(QN_BOUNDINGBOX, reader.getName())) { 479 Projection conv; 480 if (belowWMS130()) { 481 conv = Projections.getProjectionByCode(reader.getAttributeValue(WMS_NS_URL, "SRS")); 482 } else { 483 conv = Projections.getProjectionByCode(reader.getAttributeValue(WMS_NS_URL, "CRS")); 484 } 485 if (ret.getBounds() == null && conv != null) { 486 Bounds bbox = parseBoundingBox(reader, conv); 487 ret.setBounds(bbox); 488 } 489 } 490 if (tagEquals(QN_LATLONBOUNDINGBOX, reader.getName()) && belowWMS130()) { 491 if (ret.getBounds() == null) { 492 Bounds bbox = parseBoundingBox(reader, null); 493 ret.setBounds(bbox); 494 } 495 } 496 } 497 } 498 this.layers.add(ret); 499 } 500 501 /** 502 * @return if this service operates at protocol level below 1.3.0 503 */ 504 public boolean belowWMS130() { 505 return this.version.equals("1.1.1") || this.version.equals("1.1") || this.version.equals("1.0"); 506 } 507 508 private void parseAndAddStyle(XMLStreamReader reader, LayerDetails ld) throws XMLStreamException { 509 String name = null; 510 String title = null; 511 for (int event = reader.getEventType(); 512 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && tagEquals(QN_STYLE, reader.getName())); 513 event = reader.next()) { 514 if (event == XMLStreamReader.START_ELEMENT) { 515 if (tagEquals(QN_NAME, reader.getName())) { 516 name = reader.getElementText(); 517 } 518 if (tagEquals(QN_TITLE, reader.getName())) { 519 title = reader.getElementText(); 520 } 521 } 522 } 523 if (name == null) { 524 name = ""; 525 } 526 ld.addStyle(name, title); 527 } 528 529 private Bounds parseExGeographic(XMLStreamReader reader) throws XMLStreamException { 530 String minx = null, maxx = null, maxy = null, miny = null; 531 532 for (int event = reader.getEventType(); 533 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && tagEquals(QN_EX_GEOGRAPHIC_BBOX, reader.getName())); 534 event = reader.next()) { 535 if (event == XMLStreamReader.START_ELEMENT) { 536 if (tagEquals(QN_WESTBOUNDLONGITUDE, reader.getName())) { 537 minx = reader.getElementText(); 538 } 539 540 if (tagEquals(QN_EASTBOUNDLONGITUDE, reader.getName())) { 541 maxx = reader.getElementText(); 542 } 543 544 if (tagEquals(QN_SOUTHBOUNDLATITUDE, reader.getName())) { 545 miny = reader.getElementText(); 546 } 547 548 if (tagEquals(QN_NORTHBOUNDLATITUDE, reader.getName())) { 549 maxy = reader.getElementText(); 550 } 551 } 552 } 553 return parseBBox(null, miny, minx, maxy, maxx); 554 } 555 556 private Bounds parseBoundingBox(XMLStreamReader reader, Projection conv) { 557 Function<String, String> attrGetter = tag -> belowWMS130() ? 558 reader.getAttributeValue(null, tag) 559 : reader.getAttributeValue(WMS_NS_URL, tag); 560 561 return parseBBox( 562 conv, 563 attrGetter.apply("miny"), 564 attrGetter.apply("minx"), 565 attrGetter.apply("maxy"), 566 attrGetter.apply("maxx") 567 ); 568 } 569 570 private Bounds parseBBox(Projection conv, String miny, String minx, String maxy, String maxx) { 571 if (miny == null || minx == null || maxy == null || maxx == null) { 572 return null; 573 } 574 if (conv != null) { 575 new Bounds( 576 conv.eastNorth2latlon(new EastNorth(getDecimalDegree(minx), getDecimalDegree(miny))), 577 conv.eastNorth2latlon(new EastNorth(getDecimalDegree(maxx), getDecimalDegree(maxy))) 578 ); 579 } 580 return new Bounds( 581 getDecimalDegree(miny), 582 getDecimalDegree(minx), 583 getDecimalDegree(maxy), 584 getDecimalDegree(maxx) 585 ); 586 } 587 588 private static double getDecimalDegree(String value) { 589 // Some real-world WMS servers use a comma instead of a dot as decimal separator (seen in Polish WMS server) 590 return Double.parseDouble(value.replace(',', '.')); 591 } 592 593 594 private String normalizeUrl(String serviceUrlStr) throws MalformedURLException { 595 URL getCapabilitiesUrl = null; 596 String ret = null; 597 598 if (!Pattern.compile(".*GetCapabilities.*", Pattern.CASE_INSENSITIVE).matcher(serviceUrlStr).matches()) { 599 // If the url doesn't already have GetCapabilities, add it in 600 getCapabilitiesUrl = new URL(serviceUrlStr); 601 ret = serviceUrlStr; 602 if (getCapabilitiesUrl.getQuery() == null) { 603 ret = serviceUrlStr + '?' + CAPABILITIES_QUERY_STRING; 604 } else if (!getCapabilitiesUrl.getQuery().isEmpty() && !getCapabilitiesUrl.getQuery().endsWith("&")) { 605 ret = serviceUrlStr + '&' + CAPABILITIES_QUERY_STRING; 606 } else { 607 ret = serviceUrlStr + CAPABILITIES_QUERY_STRING; 608 } 609 } else { 610 // Otherwise assume it's a good URL and let the subsequent error 611 // handling systems deal with problems 612 ret = serviceUrlStr; 613 } 614 return ret; 358 } 359 360 Element capabilityElem = getChild(root, "Capability"); 361 List<Element> children = getChildren(capabilityElem, "Layer"); 362 layers = parseLayers(children, new HashSet<String>()); 363 } catch (MalformedURLException | ParserConfigurationException | SAXException e) { 364 throw new WMSGetCapabilitiesException(e, incomingData); 365 } 615 366 } 616 367 … … 641 392 } 642 393 643 644 394 static boolean imageFormatHasTransparency(final String format) { 645 395 return format != null && (format.startsWith("image/png") || format.startsWith("image/gif") … … 648 398 649 399 /** 650 * Creates ImageryInfo object from this GetCapabilities document 651 * 652 * @param name name of imagery layer 653 * @param selectedLayers layers which are to be used by this imagery layer 654 * @param selectedStyles styles that should be used for selectedLayers 655 * @param transparent if layer should be transparent 656 * @return ImageryInfo object 657 */ 658 public ImageryInfo toImageryInfo(String name, List<LayerDetails> selectedLayers, List<String> selectedStyles, boolean transparent) { 659 ImageryInfo i = new ImageryInfo(name, buildGetMapUrl(selectedLayers, selectedStyles, transparent)); 660 if (selectedLayers != null && !selectedLayers.isEmpty()) { 661 i.setServerProjections(getServerProjections(selectedLayers)); 400 * Returns a new {@code ImageryInfo} describing the given service name and selected WMS layers. 401 * @param name service name 402 * @param selectedLayers selected WMS layers 403 * @return a new {@code ImageryInfo} describing the given service name and selected WMS layers 404 */ 405 public ImageryInfo toImageryInfo(String name, Collection<LayerDetails> selectedLayers) { 406 ImageryInfo i = new ImageryInfo(name, buildGetMapUrl(selectedLayers)); 407 if (selectedLayers != null) { 408 Set<String> proj = new HashSet<>(); 409 for (WMSImagery.LayerDetails l : selectedLayers) { 410 proj.addAll(l.getProjections()); 411 } 412 i.setServerProjections(proj); 662 413 } 663 414 return i; 664 415 } 665 416 666 /** 667 * Returns projections that server supports for provided list of layers. This will be intersection of projections 668 * defined for each layer 669 * 670 * @param selectedLayers list of layers 671 * @return projection code 672 */ 673 public Collection<String> getServerProjections(List<LayerDetails> selectedLayers) { 674 if (selectedLayers.isEmpty()) { 675 return Collections.emptyList(); 676 } 677 Set<String> proj = new HashSet<>(selectedLayers.get(0).getCrs()); 678 679 // set intersect with all layers 680 for (LayerDetails ld: selectedLayers) { 681 proj.retainAll(ld.getCrs()); 682 } 683 return proj; 684 } 685 686 687 /** 688 * @param defaultLayers 689 * @return collection of LayerDetails specified by DefaultLayers 690 */ 691 public List<LayerDetails> getLayers(List<DefaultLayer> defaultLayers) { 692 Collection<String> layerNames = defaultLayers.stream().map(x -> x.getLayerName()).collect(Collectors.toList()); 693 return layers.stream() 694 .flatMap(LayerDetails::flattened) 695 .filter(x -> layerNames.contains(x.getName())) 696 .collect(Collectors.toList()); 697 } 698 699 /** 700 * @return title of this service 701 */ 702 public String getTitle() { 703 return title; 417 private List<LayerDetails> parseLayers(List<Element> children, Set<String> parentCrs) { 418 List<LayerDetails> details = new ArrayList<>(children.size()); 419 for (Element element : children) { 420 details.add(parseLayer(element, parentCrs)); 421 } 422 return details; 423 } 424 425 private LayerDetails parseLayer(Element element, Set<String> parentCrs) { 426 String name = getChildContent(element, "Title", null, null); 427 String ident = getChildContent(element, "Name", null, null); 428 String abstr = getChildContent(element, "Abstract", null, null); 429 430 // The set of supported CRS/SRS for this layer 431 Set<String> crsList = new HashSet<>(); 432 // ...including this layer's already-parsed parent projections 433 crsList.addAll(parentCrs); 434 435 // Parse the CRS/SRS pulled out of this layer's XML element 436 // I think CRS and SRS are the same at this point 437 getChildrenStream(element) 438 .filter(child -> "CRS".equals(child.getNodeName()) || "SRS".equals(child.getNodeName())) 439 .map(WMSImagery::getContent) 440 .filter(crs -> !crs.isEmpty()) 441 .map(crs -> crs.trim().toUpperCase(Locale.ENGLISH)) 442 .forEach(crsList::add); 443 444 // Check to see if any of the specified projections are supported by JOSM 445 boolean josmSupportsThisLayer = false; 446 for (String crs : crsList) { 447 josmSupportsThisLayer |= isProjSupported(crs); 448 } 449 450 Bounds bounds = null; 451 Element bboxElem = getChild(element, "EX_GeographicBoundingBox"); 452 if (bboxElem != null) { 453 // Attempt to use EX_GeographicBoundingBox for bounding box 454 double left = Double.parseDouble(getChildContent(bboxElem, "westBoundLongitude", null, null)); 455 double top = Double.parseDouble(getChildContent(bboxElem, "northBoundLatitude", null, null)); 456 double right = Double.parseDouble(getChildContent(bboxElem, "eastBoundLongitude", null, null)); 457 double bot = Double.parseDouble(getChildContent(bboxElem, "southBoundLatitude", null, null)); 458 bounds = new Bounds(bot, left, top, right); 459 } else { 460 // If that's not available, try LatLonBoundingBox 461 bboxElem = getChild(element, "LatLonBoundingBox"); 462 if (bboxElem != null) { 463 double left = getDecimalDegree(bboxElem, "minx"); 464 double top = getDecimalDegree(bboxElem, "maxy"); 465 double right = getDecimalDegree(bboxElem, "maxx"); 466 double bot = getDecimalDegree(bboxElem, "miny"); 467 bounds = new Bounds(bot, left, top, right); 468 } 469 } 470 471 List<Element> layerChildren = getChildren(element, "Layer"); 472 List<LayerDetails> childLayers = parseLayers(layerChildren, crsList); 473 474 return new LayerDetails(name, ident, abstr, crsList, josmSupportsThisLayer, bounds, childLayers); 475 } 476 477 private static double getDecimalDegree(Element elem, String attr) { 478 // Some real-world WMS servers use a comma instead of a dot as decimal separator (seen in Polish WMS server) 479 return Double.parseDouble(elem.getAttribute(attr).replace(',', '.')); 480 } 481 482 private static boolean isProjSupported(String crs) { 483 return Projections.getProjectionByCode(crs) != null; 484 } 485 486 private static String getChildContent(Element parent, String name, String missing, String empty) { 487 Element child = getChild(parent, name); 488 if (child == null) 489 return missing; 490 else { 491 String content = getContent(child); 492 return (!content.isEmpty()) ? content : empty; 493 } 494 } 495 496 private static String getContent(Element element) { 497 NodeList nl = element.getChildNodes(); 498 StringBuilder content = new StringBuilder(); 499 for (int i = 0; i < nl.getLength(); i++) { 500 Node node = nl.item(i); 501 switch (node.getNodeType()) { 502 case Node.ELEMENT_NODE: 503 content.append(getContent((Element) node)); 504 break; 505 case Node.CDATA_SECTION_NODE: 506 case Node.TEXT_NODE: 507 content.append(node.getNodeValue()); 508 break; 509 default: // Do nothing 510 } 511 } 512 return content.toString().trim(); 513 } 514 515 private static Stream<Element> getChildrenStream(Element parent) { 516 if (parent == null) { 517 // ignore missing elements 518 return Stream.empty(); 519 } else { 520 Iterable<Element> it = () -> new ChildIterator(parent); 521 return StreamSupport.stream(it.spliterator(), false); 522 } 523 } 524 525 private static Stream<Element> getChildrenStream(Element parent, String name) { 526 return getChildrenStream(parent).filter(child -> name.equals(child.getNodeName())); 527 } 528 529 private static List<Element> getChildren(Element parent, String name) { 530 return getChildrenStream(parent, name).collect(Collectors.toList()); 531 } 532 533 private static Element getChild(Element parent, String name) { 534 return getChildrenStream(parent, name).findFirst().orElse(null); 535 } 536 537 /** 538 * The details of a layer of this WMS server. 539 */ 540 public static class LayerDetails { 541 542 /** 543 * The layer name (WMS {@code Title}) 544 */ 545 public final String name; 546 /** 547 * The layer ident (WMS {@code Name}) 548 */ 549 public final String ident; 550 /** 551 * The layer abstract (WMS {@code Abstract}) 552 * @since 13199 553 */ 554 public final String abstr; 555 /** 556 * The child layers of this layer 557 */ 558 public final List<LayerDetails> children; 559 /** 560 * The bounds this layer can be used for 561 */ 562 public final Bounds bounds; 563 /** 564 * the CRS/SRS pulled out of this layer's XML element 565 */ 566 public final Set<String> crsList; 567 /** 568 * {@code true} if any of the specified projections are supported by JOSM 569 */ 570 public final boolean supported; 571 572 /** 573 * Constructs a new {@code LayerDetails}. 574 * @param name The layer name (WMS {@code Title}) 575 * @param ident The layer ident (WMS {@code Name}) 576 * @param abstr The layer abstract (WMS {@code Abstract}) 577 * @param crsList The CRS/SRS pulled out of this layer's XML element 578 * @param supportedLayer {@code true} if any of the specified projections are supported by JOSM 579 * @param bounds The bounds this layer can be used for 580 * @param childLayers The child layers of this layer 581 * @since 13199 582 */ 583 public LayerDetails(String name, String ident, String abstr, Set<String> crsList, boolean supportedLayer, Bounds bounds, 584 List<LayerDetails> childLayers) { 585 this.name = name; 586 this.ident = ident; 587 this.abstr = abstr; 588 this.supported = supportedLayer; 589 this.children = childLayers; 590 this.bounds = bounds; 591 this.crsList = crsList; 592 } 593 594 /** 595 * Determines if any of the specified projections are supported by JOSM. 596 * @return {@code true} if any of the specified projections are supported by JOSM 597 */ 598 public boolean isSupported() { 599 return this.supported; 600 } 601 602 /** 603 * Returns the CRS/SRS pulled out of this layer's XML element. 604 * @return the CRS/SRS pulled out of this layer's XML element 605 */ 606 public Set<String> getProjections() { 607 return crsList; 608 } 609 610 @Override 611 public String toString() { 612 String baseName = (name == null || name.isEmpty()) ? ident : name; 613 return abstr == null || abstr.equalsIgnoreCase(baseName) ? baseName : baseName + " (" + abstr + ')'; 614 } 704 615 } 705 616 } -
/trunk/test/unit/org/openstreetmap/josm/TestUtils.java
r13733 r13732 15 15 import java.security.AccessController; 16 16 import java.security.PrivilegedAction; 17 import java.time.Instant;18 import java.time.ZoneOffset;19 import java.time.format.DateTimeFormatter;20 import java.time.temporal.Temporal;21 17 import java.util.Arrays; 22 18 import java.util.Collection; … … 42 38 import org.openstreetmap.josm.tools.JosmRuntimeException; 43 39 import org.openstreetmap.josm.tools.Utils; 44 45 import com.github.tomakehurst.wiremock.WireMockServer;46 import com.github.tomakehurst.wiremock.core.WireMockConfiguration;47 40 48 41 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; … … 387 380 } 388 381 } 389 390 /**391 * Return WireMock server serving files under ticker directory392 * @param ticketId Ticket numeric identifier393 * @return WireMock HTTP server on dynamic port394 */395 public static WireMockServer getWireMockServer(int ticketId) {396 return new WireMockServer(397 WireMockConfiguration.options()398 .dynamicPort()399 .usingFilesUnderDirectory(getRegressionDataDir(ticketId))400 );401 }402 403 /**404 * Return WireMock server serving files under ticker directory405 * @return WireMock HTTP server on dynamic port406 */407 public static WireMockServer getWireMockServer() {408 return new WireMockServer(409 WireMockConfiguration.options()410 .dynamicPort()411 );412 }413 /**414 * Renders Temporal to RFC 1123 Date Time415 * @param time416 * @return string representation according to RFC1123 of time417 */418 public static String getHTTPDate(Temporal time) {419 return DateTimeFormatter.RFC_1123_DATE_TIME.withZone(ZoneOffset.UTC).format(time);420 }421 422 /**423 * Renders java time stamp to RFC 1123 Date Time424 * @param time425 * @return string representation according to RFC1123 of time426 */427 public static String getHTTPDate(long time) {428 return getHTTPDate(Instant.ofEpochMilli(time));429 }430 431 432 382 } -
/trunk/test/unit/org/openstreetmap/josm/actions/AddImageryLayerActionTest.java
r13733 r13732 70 70 @Test 71 71 public void testActionPerformedEnabledWms() { 72 wireMockRule.stubFor(get(urlEqualTo("/wms?SERVICE=WMS&REQUEST=GetCapabilities &VERSION=1.1.1"))72 wireMockRule.stubFor(get(urlEqualTo("/wms?VERSION=1.1.1&SERVICE=WMS&REQUEST=GetCapabilities")) 73 73 .willReturn(aResponse() 74 .withStatus(200) 75 .withHeader("Content-Type", "text/xml") 76 .withBodyFile("imagery/wms-capabilities.xml"))); 77 wireMockRule.stubFor(get(urlEqualTo("/wms?SERVICE=WMS&REQUEST=GetCapabilities")) 78 .willReturn(aResponse() 79 .withStatus(404))); 80 wireMockRule.stubFor(get(urlEqualTo("/wms?SERVICE=WMS&REQUEST=GetCapabilities&VERSION=1.3.0")) 81 .willReturn(aResponse() 82 .withStatus(404))); 83 74 .withStatus(200) 75 .withHeader("Content-Type", "text/xml") 76 .withBodyFile("imagery/wms-capabilities.xml"))); 84 77 new AddImageryLayerAction(new ImageryInfo("localhost", "http://localhost:" + wireMockRule.port() + "/wms?", 85 78 "wms_endpoint", null, null)).actionPerformed(null); -
/trunk/test/unit/org/openstreetmap/josm/data/cache/HostLimitQueueTest.java
r13733 r13732 14 14 import org.junit.Rule; 15 15 import org.junit.Test; 16 import org.openstreetmap.josm.data.imagery.TileJobOptions;17 16 import org.openstreetmap.josm.testutils.JOSMTestRules; 18 17 import org.openstreetmap.josm.tools.Logging; … … 55 54 56 55 Task(ICacheAccess<String, CacheEntry> cache, URL url, AtomicInteger counter) { 57 super(cache, new TileJobOptions(1, 1, null, 10));56 super(cache, 1, 1, null); 58 57 this.url = url; 59 58 this.counter = counter; -
/trunk/test/unit/org/openstreetmap/josm/data/cache/JCSCachedTileLoaderJobTest.java
r13733 r13732 2 2 package org.openstreetmap.josm.data.cache; 3 3 4 import static org.junit.Assert.assertArrayEquals;5 4 import static org.junit.Assert.assertEquals; 6 5 import static org.junit.Assert.assertFalse; 7 import static org.junit.Assert.assertTrue;8 6 9 7 import java.io.IOException; … … 11 9 import java.net.URL; 12 10 import java.nio.charset.StandardCharsets; 13 import java.util.concurrent.TimeUnit;14 11 15 12 import org.apache.commons.jcs.access.behavior.ICacheAccess; 16 import org.apache.commons.jcs.engine.behavior.ICacheElement;17 13 import org.junit.Before; 18 14 import org.junit.Rule; 19 15 import org.junit.Test; 20 import org.openstreetmap.josm.TestUtils;21 16 import org.openstreetmap.josm.data.cache.ICachedLoaderListener.LoadResult; 22 import org.openstreetmap.josm.data.imagery.TileJobOptions;23 17 import org.openstreetmap.josm.testutils.JOSMTestRules; 24 18 import org.openstreetmap.josm.tools.Logging; 25 26 import com.github.tomakehurst.wiremock.client.WireMock;27 import com.github.tomakehurst.wiremock.core.WireMockConfiguration;28 import com.github.tomakehurst.wiremock.junit.WireMockRule;29 import com.github.tomakehurst.wiremock.matching.UrlPattern;30 19 31 20 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; … … 36 25 public class JCSCachedTileLoaderJobTest { 37 26 38 /**39 * mocked tile server40 */41 @Rule42 public WireMockRule tileServer = new WireMockRule(WireMockConfiguration.options()43 .dynamicPort());44 45 27 private static class TestCachedTileLoaderJob extends JCSCachedTileLoaderJob<String, CacheEntry> { 46 28 private String url; 47 29 private String key; 48 30 49 TestCachedTileLoaderJob(String url, String key) { 50 this(url, key, (int) TimeUnit.DAYS.toSeconds(1)); 51 } 52 53 TestCachedTileLoaderJob(String url, String key, int minimumExpiry) { 54 super(getCache(), new TileJobOptions(30000, 30000, null, minimumExpiry)); 31 TestCachedTileLoaderJob(String url, String key) throws IOException { 32 super(getCache(), 30000, 30000, null); 55 33 56 34 this.url = url; 57 35 this.key = key; 58 36 } 59 60 37 61 38 @Override … … 75 52 @Override 76 53 protected CacheEntry createCacheEntry(byte[] content) { 77 return new CacheEntry( content);54 return new CacheEntry("dummy".getBytes(StandardCharsets.UTF_8)); 78 55 } 79 56 } … … 83 60 private boolean ready; 84 61 private LoadResult result; 85 private byte[] data;86 62 87 63 @Override … … 90 66 this.ready = true; 91 67 this.result = result; 92 if (data != null) {93 this.data = data.content;94 }95 68 this.notifyAll(); 96 69 } … … 140 113 String key = "key_unknown_host"; 141 114 TestCachedTileLoaderJob job = new TestCachedTileLoaderJob("http://unkownhost.unkownhost/unkown", key); 142 Listener listener = submitJob(job); 115 Listener listener = new Listener(); 116 job.submit(listener, true); 117 synchronized (listener) { 118 while (!listener.ready) { 119 try { 120 listener.wait(); 121 } catch (InterruptedException e1) { 122 // do nothing, still wait 123 Logging.trace(e1); 124 } 125 } 126 } 143 127 assertEquals(LoadResult.FAILURE, listener.result); // because response will be cached, and that is checked below 144 128 assertEquals("java.net.UnknownHostException: unkownhost.unkownhost", listener.attributes.getErrorMessage()); … … 151 135 152 136 job = new TestCachedTileLoaderJob("http://unkownhost.unkownhost/unkown", key); 153 listener = submitJob(job); 137 listener = new Listener(); 138 job.submit(listener, true); 139 synchronized (listener) { 140 while (!listener.ready) { 141 try { 142 listener.wait(); 143 } catch (InterruptedException e1) { 144 // do nothing, wait 145 Logging.trace(e1); 146 } 147 } 148 } 154 149 assertEquals(LoadResult.SUCCESS, listener.result); 155 150 assertFalse(job.isCacheElementValid()); 156 151 } 157 152 158 private void doTestStatusCode(int responseCode) throws IOException { 153 @SuppressFBWarnings(value = "WA_NOT_IN_LOOP") 154 private void doTestStatusCode(int responseCode) throws IOException, InterruptedException { 159 155 TestCachedTileLoaderJob job = getStatusLoaderJob(responseCode); 160 Listener listener = submitJob(job); 156 Listener listener = new Listener(); 157 job.submit(listener, true); 158 synchronized (listener) { 159 if (!listener.ready) { 160 listener.wait(); 161 } 162 } 161 163 assertEquals(responseCode, listener.attributes.getResponseCode()); 162 164 } 163 165 164 private Listener submitJob(TestCachedTileLoaderJob job) throws IOException { 165 return submitJob(job, true); 166 } 167 168 private Listener submitJob(TestCachedTileLoaderJob job, boolean force) throws IOException { 169 Listener listener = new Listener(); 170 job.submit(listener, force); 171 synchronized (listener) { 172 while (!listener.ready) { 173 try { 174 listener.wait(); 175 } catch (InterruptedException e) { 176 // do nothing, wait 177 Logging.trace(e); 178 } 179 } 180 } 181 return listener; 182 } 183 184 /** 185 * That no requst is made when entry is in cache and force == false 186 * @throws IOException 187 */ 188 @Test 189 public void testNoRequestMadeWhenEntryInCache() throws IOException { 190 ICacheAccess<String, CacheEntry> cache = getCache(); 191 long expires = TimeUnit.DAYS.toMillis(1); 192 long testStart = System.currentTimeMillis(); 193 cache.put("test", 194 new CacheEntry("cached entry".getBytes(StandardCharsets.UTF_8)), 195 createEntryAttributes(expires, 200, testStart, "eTag") 196 ); 197 createHeadGetStub(WireMock.urlEqualTo("/test"), expires, testStart, "eTag", "mock entry"); 198 199 TestCachedTileLoaderJob job = new TestCachedTileLoaderJob(tileServer.url("/test"), "test"); 200 Listener listener = submitJob(job, false); 201 tileServer.verify(0, WireMock.getRequestedFor(WireMock.anyUrl())); 202 assertArrayEquals("cached entry".getBytes(StandardCharsets.UTF_8), listener.data); 203 } 204 205 /** 206 * that request is made, when object is in cache, but force mode is used 207 * @throws IOException 208 */ 209 @Test 210 public void testRequestMadeWhenEntryInCacheAndForce() throws IOException { 211 ICacheAccess<String, CacheEntry> cache = getCache(); 212 long expires = TimeUnit.DAYS.toMillis(1); 213 long testStart = System.currentTimeMillis(); 214 cache.put("test", 215 new CacheEntry("cached dummy".getBytes(StandardCharsets.UTF_8)), 216 createEntryAttributes(expires, 200, testStart + expires, "eTag") 217 ); 218 createHeadGetStub(WireMock.urlEqualTo("/test"), expires, testStart, "eTag", "mock entry"); 219 220 TestCachedTileLoaderJob job = new TestCachedTileLoaderJob(tileServer.url("/test"), "test"); 221 Listener listener = submitJob(job, true); 222 tileServer.verify(1, WireMock.getRequestedFor(WireMock.urlEqualTo("/test"))); 223 assertArrayEquals("mock entry".getBytes(StandardCharsets.UTF_8), listener.data); 224 } 225 226 /** 227 * Mock returns no cache-control / expires headers 228 * Expire time should be set to DEFAULT_EXPIRE_TIME 229 * @throws IOException 230 */ 231 @Test 232 public void testSettingMinimumExpiryWhenNoExpires() throws IOException { 233 long testStart = System.currentTimeMillis(); 234 tileServer.stubFor( 235 WireMock.get(WireMock.urlEqualTo("/test")) 236 .willReturn(WireMock.aResponse() 237 .withBody("mock entry") 238 ) 239 ); 240 241 TestCachedTileLoaderJob job = new TestCachedTileLoaderJob(tileServer.url("/test"), "test"); 242 Listener listener = submitJob(job, false); 243 tileServer.verify(1, WireMock.getRequestedFor(WireMock.urlEqualTo("/test"))); 244 245 assertTrue("Cache entry expiration is " + (listener.attributes.getExpirationTime() - testStart) + " which is not larger than " + 246 JCSCachedTileLoaderJob.DEFAULT_EXPIRE_TIME + " (DEFAULT_EXPIRE_TIME)", 247 listener.attributes.getExpirationTime() >= testStart + JCSCachedTileLoaderJob.DEFAULT_EXPIRE_TIME); 248 249 assertTrue("Cache entry expiration is " + (listener.attributes.getExpirationTime() - System.currentTimeMillis()) + " which is not less than " + 250 JCSCachedTileLoaderJob.DEFAULT_EXPIRE_TIME + " (DEFAULT_EXPIRE_TIME)", 251 listener.attributes.getExpirationTime() <= System.currentTimeMillis() + JCSCachedTileLoaderJob.DEFAULT_EXPIRE_TIME); 252 253 assertArrayEquals("mock entry".getBytes(StandardCharsets.UTF_8), listener.data); 254 } 255 256 /** 257 * Mock returns expires headers, but Cache-Control 258 * Expire time should be set to max-age 259 * @throws IOException 260 */ 261 @Test 262 public void testSettingExpireByMaxAge() throws IOException { 263 long testStart = System.currentTimeMillis(); 264 long expires = TimeUnit.DAYS.toSeconds(1); 265 tileServer.stubFor( 266 WireMock.get(WireMock.urlEqualTo("/test")) 267 .willReturn(WireMock.aResponse() 268 .withHeader("Cache-control", "max-age=" + expires) 269 .withBody("mock entry") 270 ) 271 ); 272 273 TestCachedTileLoaderJob job = new TestCachedTileLoaderJob(tileServer.url("/test"), "test"); 274 Listener listener = submitJob(job, false); 275 tileServer.verify(1, WireMock.getRequestedFor(WireMock.urlEqualTo("/test"))); 276 277 assertTrue("Cache entry expiration is " + (listener.attributes.getExpirationTime() - testStart) + " which is not larger than " + 278 TimeUnit.SECONDS.toMillis(expires) + " (max-age)", 279 listener.attributes.getExpirationTime() >= testStart + TimeUnit.SECONDS.toMillis(expires)); 280 281 assertTrue("Cache entry expiration is " + (listener.attributes.getExpirationTime() - System.currentTimeMillis()) + " which is not less than " + 282 TimeUnit.SECONDS.toMillis(expires) + " (max-age)", 283 listener.attributes.getExpirationTime() <= System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(expires)); 284 285 assertArrayEquals("mock entry".getBytes(StandardCharsets.UTF_8), listener.data); 286 } 287 288 /** 289 * mock returns expiration: JCSCachedTileLoaderJob.DEFAULT_EXPIRE_TIME / 10 290 * minimum expire time: JCSCachedTileLoaderJob.DEFAULT_EXPIRE_TIME / 2 291 * @throws IOException 292 */ 293 @Test 294 public void testSettingMinimumExpiryByMinimumExpiryTimeLessThanDefault() throws IOException { 295 long testStart = System.currentTimeMillis(); 296 int minimumExpiryTimeSeconds = (int)(JCSCachedTileLoaderJob.DEFAULT_EXPIRE_TIME / 2); 297 298 createHeadGetStub(WireMock.urlEqualTo("/test"), (JCSCachedTileLoaderJob.DEFAULT_EXPIRE_TIME / 10), testStart, "eTag", "mock entry"); 299 300 TestCachedTileLoaderJob job = new TestCachedTileLoaderJob(tileServer.url("/test"), "test", minimumExpiryTimeSeconds); 301 Listener listener = submitJob(job, false); 302 tileServer.verify(1, WireMock.getRequestedFor(WireMock.urlEqualTo("/test"))); 303 assertArrayEquals("mock entry".getBytes(StandardCharsets.UTF_8), listener.data); 304 305 306 assertTrue("Cache entry expiration is " + (listener.attributes.getExpirationTime() - testStart) + " which is not larger than " + 307 TimeUnit.SECONDS.toMillis(minimumExpiryTimeSeconds) + " (minimumExpireTime)", 308 listener.attributes.getExpirationTime() >= testStart + TimeUnit.SECONDS.toMillis(minimumExpiryTimeSeconds) ); 309 310 assertTrue("Cache entry expiration is " + (listener.attributes.getExpirationTime() - System.currentTimeMillis()) + " which is not less than " + 311 TimeUnit.SECONDS.toMillis(minimumExpiryTimeSeconds) + " (minimumExpireTime)", 312 listener.attributes.getExpirationTime() <= System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(minimumExpiryTimeSeconds)); 313 } 314 315 /** 316 * mock returns expiration: JCSCachedTileLoaderJob.DEFAULT_EXPIRE_TIME / 10 317 * minimum expire time: JCSCachedTileLoaderJob.DEFAULT_EXPIRE_TIME * 2 318 * @throws IOException 319 */ 320 321 @Test 322 public void testSettingMinimumExpiryByMinimumExpiryTimeGreaterThanDefault() throws IOException { 323 long testStart = System.currentTimeMillis(); 324 int minimumExpiryTimeSeconds = (int)(JCSCachedTileLoaderJob.DEFAULT_EXPIRE_TIME * 2); 325 326 createHeadGetStub(WireMock.urlEqualTo("/test"), (JCSCachedTileLoaderJob.DEFAULT_EXPIRE_TIME / 10), testStart, "eTag", "mock entry"); 327 328 TestCachedTileLoaderJob job = new TestCachedTileLoaderJob(tileServer.url("/test"), "test", minimumExpiryTimeSeconds); 329 Listener listener = submitJob(job, false); 330 tileServer.verify(1, WireMock.getRequestedFor(WireMock.urlEqualTo("/test"))); 331 assertArrayEquals("mock entry".getBytes(StandardCharsets.UTF_8), listener.data); 332 333 334 assertTrue("Cache entry expiration is " + (listener.attributes.getExpirationTime() - testStart) + " which is not larger than " + 335 TimeUnit.SECONDS.toMillis(minimumExpiryTimeSeconds) + " (minimumExpireTime)", 336 listener.attributes.getExpirationTime() >= testStart + TimeUnit.SECONDS.toMillis(minimumExpiryTimeSeconds) ); 337 338 assertTrue("Cache entry expiration is " + (listener.attributes.getExpirationTime() - System.currentTimeMillis()) + " which is not less than " + 339 TimeUnit.SECONDS.toMillis(minimumExpiryTimeSeconds) + " (minimumExpireTime)", 340 listener.attributes.getExpirationTime() <= System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(minimumExpiryTimeSeconds)); 341 } 342 343 /** 344 * Check if verifying cache entries using HEAD requests work properly 345 * @throws IOException 346 */ 347 @Test 348 public void testCheckUsingHead() throws IOException { 349 ICacheAccess<String, CacheEntry> cache = getCache(); 350 long expires = TimeUnit.DAYS.toMillis(1); 351 long testStart = System.currentTimeMillis(); 352 cache.put("test", 353 new CacheEntry("cached dummy".getBytes(StandardCharsets.UTF_8)), 354 createEntryAttributes(-1 * expires, 200, testStart, "eTag--gzip") // Jetty adds --gzip to etags when compressing output 355 ); 356 357 tileServer.stubFor( 358 WireMock.get(WireMock.urlEqualTo("/test")) 359 .willReturn(WireMock.aResponse() 360 .withHeader("Expires", TestUtils.getHTTPDate(testStart + expires)) 361 .withHeader("Last-Modified", Long.toString(testStart)) 362 .withHeader("ETag", "eTag") // Jetty adds "--gzip" suffix for compressed content 363 .withBody("mock entry") 364 ) 365 ); 366 tileServer.stubFor( 367 WireMock.head(WireMock.urlEqualTo("/test")) 368 .willReturn(WireMock.aResponse() 369 .withHeader("Expires", TestUtils.getHTTPDate(testStart + expires)) 370 .withHeader("Last-Modified", Long.toString(testStart)) 371 .withHeader("ETag", "eTag--gzip") // but doesn't add to uncompressed 372 ) 373 ); 374 375 TestCachedTileLoaderJob job = new TestCachedTileLoaderJob(tileServer.url("/test"), "test"); 376 Listener listener = submitJob(job, false); // cache entry is expired, no need to force refetch 377 tileServer.verify(1, WireMock.getRequestedFor(WireMock.urlEqualTo("/test"))); 378 assertArrayEquals("mock entry".getBytes(StandardCharsets.UTF_8), listener.data); 379 380 // cache entry should be retrieved from cache 381 listener = submitJob(job, false); 382 tileServer.verify(1, WireMock.getRequestedFor(WireMock.urlEqualTo("/test"))); 383 assertArrayEquals("mock entry".getBytes(StandardCharsets.UTF_8), listener.data); 384 385 // invalidate entry in cache 386 ICacheElement<String, CacheEntry> cacheEntry = cache.getCacheElement("test"); 387 CacheEntryAttributes attributes = (CacheEntryAttributes)cacheEntry.getElementAttributes(); 388 attributes.setExpirationTime(testStart - TimeUnit.DAYS.toMillis(1)); 389 cache.put("test", cacheEntry.getVal(), attributes); 390 391 // because cache entry is invalid - HEAD request shall be made 392 tileServer.verify(0, WireMock.headRequestedFor(WireMock.urlEqualTo("/test"))); // no head requests were made until now 393 listener = submitJob(job, false); 394 tileServer.verify(1, WireMock.headRequestedFor(WireMock.urlEqualTo("/test"))); // verify head requests were made 395 tileServer.verify(1, WireMock.getRequestedFor(WireMock.urlEqualTo("/test"))); // verify no more get requests were made 396 assertArrayEquals("mock entry".getBytes(StandardCharsets.UTF_8), listener.data); 397 assertTrue(listener.attributes.getExpirationTime() >= testStart + expires); 398 399 // cache entry should be retrieved from cache 400 listener = submitJob(job, false); // cache entry is expired, no need to force refetch 401 tileServer.verify(1, WireMock.getRequestedFor(WireMock.urlEqualTo("/test"))); 402 tileServer.verify(1, WireMock.getRequestedFor(WireMock.urlEqualTo("/test"))); 403 assertArrayEquals("mock entry".getBytes(StandardCharsets.UTF_8), listener.data); 404 } 405 406 /** 407 * Check if server returns 304 - it will update cache attributes and not ask again for it 408 * @throws IOException 409 */ 410 @Test 411 public void testCheckUsing304() throws IOException { 412 ICacheAccess<String, CacheEntry> cache = getCache(); 413 long expires = TimeUnit.DAYS.toMillis(1); 414 long testStart = System.currentTimeMillis(); 415 cache.put("test", 416 new CacheEntry("cached dummy".getBytes(StandardCharsets.UTF_8)), 417 createEntryAttributes(-1 * expires, 200, testStart, "eTag") 418 ); 419 420 tileServer.stubFor( 421 WireMock.get(WireMock.urlEqualTo("/test")) 422 .willReturn(WireMock.status(304) 423 .withHeader("Expires", TestUtils.getHTTPDate(testStart + expires)) 424 .withHeader("Last-Modified", Long.toString(testStart)) 425 .withHeader("ETag", "eTag") 426 ) 427 ); 428 429 TestCachedTileLoaderJob job = new TestCachedTileLoaderJob(tileServer.url("/test"), "test"); 430 Listener listener = submitJob(job, false); 431 tileServer.verify(1, WireMock.getRequestedFor(WireMock.urlEqualTo("/test"))); 432 assertArrayEquals("cached dummy".getBytes(StandardCharsets.UTF_8), listener.data); 433 assertTrue(testStart + expires <= listener.attributes.getExpirationTime()); 434 listener = submitJob(job, false); 435 tileServer.verify(1, WireMock.getRequestedFor(WireMock.urlEqualTo("/test"))); // no more requests were made 436 } 437 438 private void createHeadGetStub(UrlPattern url, long expires, long lastModified, String eTag, String body) { 439 tileServer.stubFor( 440 WireMock.get(url) 441 .willReturn(WireMock.aResponse() 442 .withHeader("Expires", TestUtils.getHTTPDate(lastModified + expires)) 443 .withHeader("Last-Modified", Long.toString(lastModified)) 444 .withHeader("ETag", eTag) 445 .withBody(body) 446 ) 447 ); 448 tileServer.stubFor( 449 WireMock.head(url) 450 .willReturn(WireMock.aResponse() 451 .withHeader("Expires", TestUtils.getHTTPDate(lastModified + expires)) 452 .withHeader("Last-Modified", Long.toString(lastModified)) 453 .withHeader("ETag", eTag) 454 ) 455 ); 456 } 457 458 private CacheEntryAttributes createEntryAttributes(long maxAge, int responseCode, String eTag) { 459 long validTo = maxAge + System.currentTimeMillis(); 460 return createEntryAttributes(maxAge, responseCode, validTo, eTag); 461 } 462 463 private CacheEntryAttributes createEntryAttributes(long expirationTime, int responseCode, long lastModification, String eTag) { 464 CacheEntryAttributes entryAttributes = new CacheEntryAttributes(); 465 entryAttributes.setExpirationTime(lastModification + expirationTime); 466 entryAttributes.setResponseCode(responseCode); 467 entryAttributes.setLastModification(lastModification); 468 entryAttributes.setEtag(eTag); 469 return entryAttributes; 470 } 471 472 private static TestCachedTileLoaderJob getStatusLoaderJob(int responseCode) { 166 private static TestCachedTileLoaderJob getStatusLoaderJob(int responseCode) throws IOException { 473 167 return new TestCachedTileLoaderJob("http://httpstat.us/" + responseCode, "key_" + responseCode); 474 168 } 475 169 476 private static ICacheAccess<String, CacheEntry> getCache() { 170 private static ICacheAccess<String, CacheEntry> getCache() throws IOException { 477 171 return JCSCacheManager.getCache("test"); 478 172 } -
/trunk/test/unit/org/openstreetmap/josm/data/imagery/TMSCachedTileLoaderJobTest.java
r13733 r13732 2 2 package org.openstreetmap.josm.data.imagery; 3 3 4 import static org.junit.Assert.assertArrayEquals;5 4 import static org.junit.Assert.assertEquals; 6 5 import static org.junit.Assert.assertTrue; 7 6 8 import java.io.IOException;9 import java.net.MalformedURLException;10 import java.net.URL;11 import java.nio.charset.StandardCharsets;12 import java.util.concurrent.Executors;13 import java.util.concurrent.ThreadPoolExecutor;14 import java.util.concurrent.TimeUnit;15 7 import java.util.regex.Matcher; 16 8 17 import org.apache.commons.jcs.access.behavior.ICacheAccess;18 import org.junit.Before;19 9 import org.junit.Rule; 20 10 import org.junit.Test; 21 import org.openstreetmap.gui.jmapviewer.Tile;22 import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener;23 import org.openstreetmap.gui.jmapviewer.tilesources.TMSTileSource;24 import org.openstreetmap.josm.TestUtils;25 import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry;26 import org.openstreetmap.josm.data.cache.CacheEntryAttributes;27 import org.openstreetmap.josm.data.cache.JCSCacheManager;28 11 import org.openstreetmap.josm.testutils.JOSMTestRules; 29 import org.openstreetmap.josm.tools.Logging;30 12 import org.openstreetmap.josm.tools.Utils; 31 32 import com.github.tomakehurst.wiremock.client.WireMock;33 import com.github.tomakehurst.wiremock.core.WireMockConfiguration;34 import com.github.tomakehurst.wiremock.junit.WireMockRule;35 13 36 14 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; … … 46 24 @Rule 47 25 @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD") 48 public JOSMTestRules test = new JOSMTestRules().preferences(); 49 50 /** 51 * mocked tile server 52 */ 53 @Rule 54 public WireMockRule tileServer = new WireMockRule(WireMockConfiguration.options() 55 .dynamicPort()); 56 57 @Before 58 public void clearCache() throws Exception { 59 getCache().clear(); 60 } 61 62 private static ICacheAccess<String, BufferedImageCacheEntry> getCache() { 63 return JCSCacheManager.getCache("test"); 64 } 65 66 private static class TestCachedTileLoaderJob extends TMSCachedTileLoaderJob { 67 private String url; 68 private String key; 69 70 TestCachedTileLoaderJob(TileLoaderListener listener, Tile tile, String key) throws IOException { 71 this(listener, tile, key, (int) TimeUnit.DAYS.toSeconds(1)); 72 } 73 74 TestCachedTileLoaderJob(TileLoaderListener listener, Tile tile, String key, int minimumExpiry) throws IOException { 75 super(listener, tile, getCache(), new TileJobOptions(30000, 30000, null, minimumExpiry), 76 (ThreadPoolExecutor) Executors.newFixedThreadPool(1)); 77 78 this.url = tile.getUrl(); 79 this.key = key; 80 } 81 82 @Override 83 public URL getUrl() { 84 try { 85 return new URL(url); 86 } catch (MalformedURLException e) { 87 throw new RuntimeException(e); 88 } 89 } 90 91 @Override 92 protected BufferedImageCacheEntry createCacheEntry(byte[] content) { 93 return new BufferedImageCacheEntry(content); 94 } 95 96 public CacheEntryAttributes getAttributes() { 97 return attributes; 98 } 99 100 @Override 101 public boolean isObjectLoadable() { 102 // use implementation from grand parent, to avoid calling getImage on dummy data 103 if (cacheData == null) { 104 return false; 105 } 106 return cacheData.getContent().length > 0; } 107 } 108 109 private static class Listener implements TileLoaderListener { 110 private CacheEntryAttributes attributes; 111 private boolean ready; 112 private byte[] data; 113 114 115 @Override 116 public synchronized void tileLoadingFinished(Tile tile, boolean success) { 117 ready = true; 118 this.notifyAll(); 119 } 120 } 121 122 private static class MockTile extends Tile { 123 MockTile(String url) { 124 super(new MockTileSource(url), 0, 0, 0); 125 } 126 } 127 128 private static class MockTileSource extends TMSTileSource { 129 private final String url; 130 131 public MockTileSource(String url) { 132 super(new ImageryInfo("mock")); 133 this.url = url; 134 } 135 136 @Override 137 public String getTileUrl(int zoom, int tilex, int tiley) throws IOException { 138 return url; 139 } 140 } 26 public JOSMTestRules test = new JOSMTestRules(); 141 27 142 28 /** … … 167 53 assertEquals(expected, Utils.strip(m.group(1))); 168 54 } 169 170 private TestCachedTileLoaderJob submitJob(MockTile tile, String key, boolean force) throws IOException {171 return submitJob(tile, key, 0, force);172 }173 174 private TestCachedTileLoaderJob submitJob(MockTile tile, String key, int minimumExpiry, boolean force) throws IOException {175 Listener listener = new Listener();176 TestCachedTileLoaderJob job = new TestCachedTileLoaderJob(listener, tile, key, minimumExpiry);177 job.submit(force);178 synchronized (listener) {179 while (!listener.ready) {180 try {181 listener.wait();182 } catch (InterruptedException e) {183 // do nothing, wait184 Logging.trace(e);185 }186 }187 }188 return job;189 }190 191 /**192 * When tile server doesn't return any Expires/Cache-Control headers, expire should be at least MINIMUM_EXPIRES193 * @throws IOException194 */195 @Test196 public void testNoCacheHeaders() throws IOException {197 long testStart = System.currentTimeMillis();198 tileServer.stubFor(199 WireMock.get(WireMock.urlEqualTo("/test"))200 .willReturn(WireMock.aResponse()201 .withBody("mock entry")202 )203 );204 205 TestCachedTileLoaderJob job = submitJob(new MockTile(tileServer.url("/test")), "test", false);206 assertExpirationAtLeast(testStart + TMSCachedTileLoaderJob.MINIMUM_EXPIRES.get(), job);207 assertArrayEquals("mock entry".getBytes(StandardCharsets.UTF_8), job.get().getContent());208 job = submitJob(new MockTile(tileServer.url("/test")), "test", false); // submit another job for the same tile209 // only one request to tile server should be made, second should come from cache210 tileServer.verify(1, WireMock.getRequestedFor(WireMock.urlEqualTo("/test")));211 assertArrayEquals("mock entry".getBytes(StandardCharsets.UTF_8), job.get().getContent());212 }213 214 /**215 * When tile server doesn't return any Expires/Cache-Control headers, expire should be at least minimumExpires parameter216 * @throws IOException217 */218 @Test219 public void testNoCacheHeadersMinimumExpires() throws IOException {220 noCacheHeadersMinimumExpires((int) TimeUnit.MILLISECONDS.toSeconds(TMSCachedTileLoaderJob.MINIMUM_EXPIRES.get() * 2));221 }222 223 /**224 * When tile server doesn't return any Expires/Cache-Control headers, expire should be at least minimumExpires parameter,225 * which is larger than MAXIMUM_EXPIRES226 * @throws IOException227 */228 229 @Test230 public void testNoCacheHeadersMinimumExpiresLargerThanMaximum() throws IOException {231 noCacheHeadersMinimumExpires((int) TimeUnit.MILLISECONDS.toSeconds(TMSCachedTileLoaderJob.MAXIMUM_EXPIRES.get() * 2));232 }233 234 private void noCacheHeadersMinimumExpires(int minimumExpires) throws IOException {235 long testStart = System.currentTimeMillis();236 tileServer.stubFor(237 WireMock.get(WireMock.urlEqualTo("/test"))238 .willReturn(WireMock.aResponse()239 .withBody("mock entry")240 )241 );242 TestCachedTileLoaderJob job = submitJob(new MockTile(tileServer.url("/test")), "test", minimumExpires, false);243 assertExpirationAtLeast(testStart + minimumExpires, job);244 assertArrayEquals("mock entry".getBytes(StandardCharsets.UTF_8), job.get().getContent());245 job = submitJob(new MockTile(tileServer.url("/test")), "test", false); // submit another job for the same tile246 // only one request to tile server should be made, second should come from cache247 tileServer.verify(1, WireMock.getRequestedFor(WireMock.urlEqualTo("/test")));248 assertArrayEquals("mock entry".getBytes(StandardCharsets.UTF_8), job.get().getContent());249 }250 251 /**252 * When tile server returns Expires header shorter than MINIMUM_EXPIRES, we should cache if for at least MINIMUM_EXPIRES253 * @throws IOException254 */255 @Test256 public void testShortExpire() throws IOException {257 long testStart = System.currentTimeMillis();258 long expires = TMSCachedTileLoaderJob.MINIMUM_EXPIRES.get() / 2;259 tileServer.stubFor(260 WireMock.get(WireMock.urlEqualTo("/test"))261 .willReturn(WireMock.aResponse()262 .withHeader("Expires", TestUtils.getHTTPDate(testStart + expires))263 .withBody("mock entry")264 )265 );266 TestCachedTileLoaderJob job = submitJob(new MockTile(tileServer.url("/test")), "test", false);267 assertExpirationAtLeast(testStart + TMSCachedTileLoaderJob.MINIMUM_EXPIRES.get(), job);268 assertArrayEquals("mock entry".getBytes(StandardCharsets.UTF_8), job.get().getContent());269 job = submitJob(new MockTile(tileServer.url("/test")), "test", false); // submit another job for the same tile270 // only one request to tile server should be made, second should come from cache271 tileServer.verify(1, WireMock.getRequestedFor(WireMock.urlEqualTo("/test")));272 assertArrayEquals("mock entry".getBytes(StandardCharsets.UTF_8), job.get().getContent());273 }274 275 private void assertExpirationAtLeast(long duration, TestCachedTileLoaderJob job) {276 assertTrue(277 "Expiration time shorter by " +278 -1 * (job.getAttributes().getExpirationTime() - duration) +279 " than expected",280 job.getAttributes().getExpirationTime() >= duration);281 }282 283 private void assertExpirationAtMost(long duration, TestCachedTileLoaderJob job) {284 assertTrue(285 "Expiration time longer by " +286 (job.getAttributes().getExpirationTime() - duration) +287 " than expected",288 job.getAttributes().getExpirationTime() <= duration);289 }290 291 292 @Test293 public void testLongExpire() throws IOException {294 long testStart = System.currentTimeMillis();295 long expires = TMSCachedTileLoaderJob.MAXIMUM_EXPIRES.get() * 2;296 tileServer.stubFor(297 WireMock.get(WireMock.urlEqualTo("/test"))298 .willReturn(WireMock.aResponse()299 .withHeader("Expires", TestUtils.getHTTPDate(testStart + expires))300 .withBody("mock entry")301 )302 );303 TestCachedTileLoaderJob job = submitJob(new MockTile(tileServer.url("/test")), "test", false);304 // give 1 second margin305 assertExpirationAtMost(testStart + TMSCachedTileLoaderJob.MAXIMUM_EXPIRES.get() + TimeUnit.SECONDS.toMillis(1), job);306 307 assertArrayEquals("mock entry".getBytes(StandardCharsets.UTF_8), job.get().getContent());308 job = submitJob(new MockTile(tileServer.url("/test")), "test", false); // submit another job for the same tile309 // only one request to tile server should be made, second should come from cache310 tileServer.verify(1, WireMock.getRequestedFor(WireMock.urlEqualTo("/test")));311 assertArrayEquals("mock entry".getBytes(StandardCharsets.UTF_8), job.get().getContent());312 }313 314 55 } -
/trunk/test/unit/org/openstreetmap/josm/data/imagery/WMTSTileSourceTest.java
r13733 r13732 8 8 import java.io.IOException; 9 9 import java.net.MalformedURLException; 10 import java.nio.file.Files;11 import java.nio.file.Paths;12 10 import java.util.ArrayList; 13 import java.util.Arrays; 14 import java.util.List; 15 16 import org.junit.ClassRule; 11 import java.util.Collection; 12 17 13 import org.junit.Ignore; 14 import org.junit.Rule; 18 15 import org.junit.Test; 19 16 import org.openstreetmap.gui.jmapviewer.tilesources.TemplatedTMSTileSource; … … 22 19 import org.openstreetmap.josm.data.Bounds; 23 20 import org.openstreetmap.josm.data.coor.LatLon; 24 import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType;25 import org.openstreetmap.josm.data.imagery.WMTSTileSource.WMTSGetCapabilitiesException;26 21 import org.openstreetmap.josm.data.projection.Projections; 27 import org.openstreetmap.josm.spi.preferences.Config;28 22 import org.openstreetmap.josm.testutils.JOSMTestRules; 29 30 import com.github.tomakehurst.wiremock.WireMockServer;31 import com.github.tomakehurst.wiremock.client.WireMock;32 23 33 24 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; … … 37 28 */ 38 29 public class WMTSTileSourceTest { 39 40 /**41 * Setup test.42 */43 @ClassRule44 @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")45 public static JOSMTestRules test = new JOSMTestRules().preferences().platform();46 30 47 31 private ImageryInfo testImageryTMS = new ImageryInfo("test imagery", "http://localhost", "tms", null, null); … … 60 44 "wmts/bug13975-multiple-tile-matrices-for-one-layer-projection.xml"); 61 45 46 /** 47 * Setup test. 48 */ 49 @Rule 50 @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD") 51 public JOSMTestRules test = new JOSMTestRules(); 62 52 63 53 private static ImageryInfo getImagery(String path) { 64 54 try { 65 ImageryInfo ret =new ImageryInfo(55 return new ImageryInfo( 66 56 "test", 67 57 new File(path).toURI().toURL().toString() 68 58 ); 69 ret.setImageryType(ImageryType.WMTS);70 return ret;71 59 } catch (MalformedURLException e) { 72 60 e.printStackTrace(); … … 76 64 77 65 @Test 78 public void testPseudoMercator() throws IOException , WMTSGetCapabilitiesException{66 public void testPseudoMercator() throws IOException { 79 67 Main.setProjection(Projections.getProjectionByCode("EPSG:3857")); 80 68 WMTSTileSource testSource = new WMTSTileSource(testImageryPSEUDO_MERCATOR); … … 106 94 107 95 @Test 108 public void testWALLONIE() throws IOException , WMTSGetCapabilitiesException{96 public void testWALLONIE() throws IOException { 109 97 Main.setProjection(Projections.getProjectionByCode("EPSG:31370")); 110 98 WMTSTileSource testSource = new WMTSTileSource(testImageryWALLONIE); … … 126 114 @Test 127 115 @Ignore("disable this test, needs further working") // XXX 128 public void testWALLONIENoMatrixDimension() throws IOException , WMTSGetCapabilitiesException{116 public void testWALLONIENoMatrixDimension() throws IOException { 129 117 Main.setProjection(Projections.getProjectionByCode("EPSG:31370")); 130 118 WMTSTileSource testSource = new WMTSTileSource(getImagery("test/data/wmts/WMTSCapabilities-Wallonie-nomatrixdimension.xml")); … … 150 138 151 139 @Test 152 public void testWIEN() throws IOException , WMTSGetCapabilitiesException{140 public void testWIEN() throws IOException { 153 141 Main.setProjection(Projections.getProjectionByCode("EPSG:3857")); 154 142 WMTSTileSource testSource = new WMTSTileSource(testImageryWIEN); … … 192 180 193 181 @Test 194 public void testGeoportalTOPOPL() throws IOException , WMTSGetCapabilitiesException{182 public void testGeoportalTOPOPL() throws IOException { 195 183 Main.setProjection(Projections.getProjectionByCode("EPSG:4326")); 196 184 WMTSTileSource testSource = new WMTSTileSource(testImageryTOPO_PL); … … 214 202 215 203 @Test 216 public void testGeoportalORTOPL4326() throws IOException , WMTSGetCapabilitiesException{204 public void testGeoportalORTOPL4326() throws IOException { 217 205 Main.setProjection(Projections.getProjectionByCode("EPSG:4326")); 218 206 WMTSTileSource testSource = new WMTSTileSource(testImageryORTO_PL); … … 223 211 224 212 @Test 225 public void testGeoportalORTOPL2180() throws IOException , WMTSGetCapabilitiesException{213 public void testGeoportalORTOPL2180() throws IOException { 226 214 Main.setProjection(Projections.getProjectionByCode("EPSG:2180")); 227 215 WMTSTileSource testSource = new WMTSTileSource(testImageryORTO_PL); … … 233 221 234 222 @Test 235 public void testTicket12168() throws IOException , WMTSGetCapabilitiesException{223 public void testTicket12168() throws IOException { 236 224 Main.setProjection(Projections.getProjectionByCode("EPSG:3857")); 237 225 WMTSTileSource testSource = new WMTSTileSource(testImagery12168); … … 243 231 244 232 @Test 233 @Ignore("disabled as this needs user action") // XXX 245 234 public void testTwoTileSetsForOneProjection() throws Exception { 246 235 Main.setProjection(Projections.getProjectionByCode("EPSG:3857")); 247 ImageryInfo ontario = getImagery(TestUtils.getTestDataRoot() + "wmts/WMTSCapabilities-Ontario.xml"); 248 ontario.setDefaultLayers(Arrays.asList(new DefaultLayer[] { 249 new DefaultLayer(ImageryType.WMTS, "Basemap_Imagery_2014", null, "default028mm") 250 })); 251 WMTSTileSource testSource = new WMTSTileSource(ontario); 252 testSource.initProjection(Main.getProjection()); 253 assertEquals( 254 "http://maps.ottawa.ca/arcgis/rest/services/Basemap_Imagery_2014/MapServer/WMTS/tile/1.0.0/Basemap_Imagery_2014/default/default028mm/4/2932/2371.jpg", 255 testSource.getTileUrl(4, 2371, 2932)); 256 verifyTile(new LatLon(45.4601306, -75.7617187), testSource, 2372, 2932, 4); 257 verifyTile(new LatLon(45.4602510, -75.7617187), testSource, 607232, 750591, 12); 258 } 259 260 @Test 261 public void testTwoTileSetsForOneProjectionSecondLayer() throws Exception { 262 Main.setProjection(Projections.getProjectionByCode("EPSG:3857")); 263 ImageryInfo ontario = getImagery(TestUtils.getTestDataRoot() + "wmts/WMTSCapabilities-Ontario.xml"); 264 ontario.setDefaultLayers(Arrays.asList(new DefaultLayer[] { 265 new DefaultLayer(ImageryType.WMTS, "Basemap_Imagery_2014", null, "GoogleMapsCompatible") 266 })); 267 WMTSTileSource testSource = new WMTSTileSource(ontario); 268 testSource.initProjection(Main.getProjection()); 269 assertEquals( 270 "http://maps.ottawa.ca/arcgis/rest/services/Basemap_Imagery_2014/MapServer/WMTS/tile/1.0.0/Basemap_Imagery_2014/default/GoogleMapsCompatible/4/2932/2371.jpg", 271 testSource.getTileUrl(4, 2371, 2932)); 272 verifyMercatorTile(testSource, 74, 91, 8); 273 verifyMercatorTile(testSource, 37952, 46912, 17); 274 } 275 276 @Test 236 WMTSTileSource testSource = new WMTSTileSource(testImageryOntario); 237 testSource.initProjection(Main.getProjection()); 238 verifyTile(new LatLon(45.4105023, -75.7153702), testSource, 303751, 375502, 12); 239 verifyTile(new LatLon(45.4601306, -75.7617187), testSource, 1186, 1466, 4); 240 } 241 242 @Test 243 @Ignore("disabled as this needs user action") // XXX 277 244 public void testManyLayersScrollbars() throws Exception { 278 245 Main.setProjection(Projections.getProjectionByCode("EPSG:3857")); … … 304 271 Main.setProjection(Projections.getProjectionByCode("EPSG:3857")); 305 272 ImageryInfo copy = new ImageryInfo(testMultipleTileMatrixForLayer); 306 List<DefaultLayer> defaultLayers = new ArrayList<>(1);307 defaultLayers.add(new DefaultLayer( ImageryType.WMTS,"Mashhad_BaseMap_1", null, "default028mm"));273 Collection<DefaultLayer> defaultLayers = new ArrayList<>(1); 274 defaultLayers.add(new WMTSDefaultLayer("Mashhad_BaseMap_1", "default028mm")); 308 275 copy.setDefaultLayers(defaultLayers); 309 276 WMTSTileSource testSource = new WMTSTileSource(copy); … … 319 286 * Test WMTS dimension. 320 287 * @throws IOException if any I/O error occurs 321 * @throws WMTSGetCapabilitiesException322 288 */ 323 289 @Test 324 public void testDimension() throws IOException , WMTSGetCapabilitiesException{290 public void testDimension() throws IOException { 325 291 Main.setProjection(Projections.getProjectionByCode("EPSG:21781")); 326 292 ImageryInfo info = new ImageryInfo(testImageryGeoAdminCh); 327 List<DefaultLayer> defaultLayers = new ArrayList<>(1);328 defaultLayers.add(new DefaultLayer( ImageryType.WMTS,"ch.are.agglomerationen_isolierte_staedte", null, "21781_26"));293 Collection<DefaultLayer> defaultLayers = new ArrayList<>(1); 294 defaultLayers.add(new WMTSDefaultLayer("ch.are.agglomerationen_isolierte_staedte", "21781_26")); 329 295 info.setDefaultLayers(defaultLayers); 330 296 WMTSTileSource testSource = new WMTSTileSource(info); … … 334 300 testSource.getTileUrl(1, 2, 3) 335 301 ); 336 }337 338 @Test339 public void testDefaultLayer() throws Exception {340 // https://gibs.earthdata.nasa.gov/wmts/epsg3857/best/1.0.0/WMTSCapabilities.xml341 WireMockServer getCapabilitiesMock = TestUtils.getWireMockServer();342 String getCapabilitiesBody = new String(Files.readAllBytes(Paths.get(TestUtils.getTestDataRoot() + "wmts/getCapabilities-lots-of-layers.xml")), "UTF-8");343 // do not use withFileBody as it needs different directory layout :(344 getCapabilitiesMock.stubFor(WireMock.get(WireMock.anyUrl()).willReturn(WireMock.aResponse().withBody(getCapabilitiesBody)));345 getCapabilitiesMock.start();346 347 WireMockServer mapsMock = TestUtils.getWireMockServer();348 mapsMock.stubFor(WireMock.get(WireMock.anyUrl()).willReturn(WireMock.aResponse().withBody(349 "<?xml version='1.0' encoding='UTF-8'?>\n" +350 "<imagery xmlns=\"http://josm.openstreetmap.de/maps-1.0\">\n" +351 "<entry>\n" +352 "<name>Landsat</name>\n" +353 "<id>landsat</id>\n" +354 "<type>wmts</type>\n" +355 "<url><![CDATA[" + getCapabilitiesMock.url("/getcapabilities.xml") + "]]></url>\n" +356 "<defaultLayers>" +357 "<layer name=\"GEOGRAPHICALGRIDSYSTEMS.MAPS\" />" +358 "</defaultLayers>" +359 "</entry>\n" +360 "</imagery>"361 )));362 mapsMock.start();363 Config.getPref().put("josm.url", mapsMock.url("/"));364 365 ImageryLayerInfo.instance.loadDefaults(true, null, false);366 367 assertEquals(1, ImageryLayerInfo.instance.getDefaultLayers().size());368 ImageryInfo wmtsImageryInfo = ImageryLayerInfo.instance.getDefaultLayers().get(0);369 assertEquals(1, wmtsImageryInfo.getDefaultLayers().size());370 assertEquals("GEOGRAPHICALGRIDSYSTEMS.MAPS", wmtsImageryInfo.getDefaultLayers().get(0).getLayerName());371 WMTSTileSource tileSource = new WMTSTileSource(wmtsImageryInfo);372 tileSource.initProjection(Projections.getProjectionByCode("EPSG:3857"));373 assertEquals("http://wxs.ign.fr/61fs25ymczag0c67naqvvmap/geoportail/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&"374 + "LAYER=GEOGRAPHICALGRIDSYSTEMS.MAPS"375 + "&STYLE=normal&FORMAT=image/jpeg&tileMatrixSet=PM&tileMatrix=1&tileRow=1&tileCol=1", tileSource.getTileUrl(1, 1, 1));376 377 302 } 378 303 -
/trunk/test/unit/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayerTest.java
r13733 r13732 120 120 return new TileLoaderFactory() { 121 121 @Override 122 public TileLoader makeTileLoader(TileLoaderListener listener, Map<String, String> headers, 123 long minimumExpiryTime) { 122 public TileLoader makeTileLoader(TileLoaderListener listener, Map<String, String> headers) { 124 123 return null; 125 124 } -
/trunk/test/unit/org/openstreetmap/josm/gui/layer/WMTSLayerTest.java
r13733 r13732 22 22 @Rule 23 23 @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD") 24 public JOSMTestRules test = new JOSMTestRules(). preferences().timeout(20000);24 public JOSMTestRules test = new JOSMTestRules().timeout(20000); 25 25 26 26 /** -
/trunk/test/unit/org/openstreetmap/josm/io/imagery/WMSImageryTest.java
r13733 r13732 6 6 7 7 import java.io.IOException; 8 import java.nio.file.Files; 9 import java.nio.file.Path; 10 import java.nio.file.Paths; 11 import java.util.List; 8 import java.io.InputStream; 12 9 13 10 import org.junit.Rule; … … 16 13 import org.openstreetmap.josm.io.imagery.WMSImagery.WMSGetCapabilitiesException; 17 14 import org.openstreetmap.josm.testutils.JOSMTestRules; 18 19 import com.github.tomakehurst.wiremock.WireMockServer;20 import com.github.tomakehurst.wiremock.client.WireMock;21 15 22 16 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; … … 32 26 @Rule 33 27 @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD") 34 public JOSMTestRules test = new JOSMTestRules() .platform().projection();28 public JOSMTestRules test = new JOSMTestRules(); 35 29 36 30 /** … … 55 49 @Test 56 50 public void testTicket15730() throws IOException, WMSGetCapabilitiesException { 57 WireMockServer wm = TestUtils.getWireMockServer(15730); 58 wm.stubFor(WireMock.get(WireMock.anyUrl()).willReturn(WireMock.aResponse().withBodyFile("capabilities.xml"))); 59 wm.start(); 60 WMSImagery wms = new WMSImagery(wm.url("capabilities.xml")); 61 assertEquals(1, wms.getLayers().size()); 62 assertTrue(wms.getLayers().get(0).getAbstract().startsWith("South Carolina NAIP Imagery 2017 Resolution: 100CM ")); 63 wm.shutdown(); 64 } 65 66 @Test 67 public void testNestedLayers() throws Exception { 68 WireMockServer getCapabilitiesMock = TestUtils.getWireMockServer(); 69 String getCapabilitiesBody = new String(Files.readAllBytes(Paths.get(TestUtils.getTestDataRoot() + "wms/mapa-um-warszawa-pl.xml")), "UTF-8"); 70 getCapabilitiesMock.stubFor(WireMock.get(WireMock.anyUrl()).willReturn(WireMock.aResponse().withBody(getCapabilitiesBody))); 71 getCapabilitiesMock.start(); 72 WMSImagery wmsi = new WMSImagery(getCapabilitiesMock.url("/serwis")); 73 assertEquals(1, wmsi.getLayers().size()); 74 assertEquals("Server WMS m.st. Warszawy", wmsi.getLayers().get(0).toString()); 75 assertEquals(202, wmsi.getLayers().get(0).getChildren().size()); 51 try (InputStream is = TestUtils.getRegressionDataStream(15730, "capabilities.xml")) { 52 WMSImagery wms = new WMSImagery(); 53 wms.parseCapabilities(null, is); 54 assertEquals(1, wms.getLayers().size()); 55 assertTrue(wms.getLayers().get(0).abstr.startsWith("South Carolina NAIP Imagery 2017 Resolution: 100CM ")); 56 } 76 57 } 77 58 … … 83 64 @Test 84 65 public void testTicket16248() throws IOException, WMSGetCapabilitiesException { 85 Path capabilitiesPath = Paths.get(TestUtils.getRegressionDataFile(16248, "capabilities.xml")); 86 WireMockServer getCapabilitiesMock = TestUtils.getWireMockServer(); 87 getCapabilitiesMock.stubFor( 88 WireMock.get(WireMock.anyUrl()) 89 .willReturn(WireMock.aResponse().withBody(Files.readAllBytes(capabilitiesPath)))); 90 getCapabilitiesMock.start(); 91 WMSImagery wms = new WMSImagery(getCapabilitiesMock.url("any")); 92 assertEquals("http://wms.hgis.cartomatic.pl/topo/3857/m25k", wms.buildRootUrl()); 93 assertEquals("wms.hgis.cartomatic.pl", wms.getLayers().get(0).getName()); 94 assertEquals("http://wms.hgis.cartomatic.pl/topo/3857/m25kFORMAT=image/png&TRANSPARENT=TRUE&VERSION=1.1.1&SERVICE=WMS&REQUEST=GetMap&" 95 + "LAYERS=wms.hgis.cartomatic.pl&STYLES=&SRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}", 96 wms.buildGetMapUrl(wms.getLayers(), (List<String>)null, true)); 66 try (InputStream is = TestUtils.getRegressionDataStream(16248, "capabilities.xml")) { 67 WMSImagery wms = new WMSImagery(); 68 wms.parseCapabilities(null, is); 69 assertEquals("http://wms.hgis.cartomatic.pl/topo/3857/m25k", wms.getServiceUrl().toExternalForm()); 70 } 97 71 } 98 72 } 99
Note:
See TracChangeset
for help on using the changeset viewer.
