Changes in / [13733:13732] in josm


Ignore:
Location:
/trunk
Files:
1 added
7 deleted
30 edited

Legend:

Unmodified
Added
Removed
  • /trunk/data/maps.xsd

    r13733 r13732  
    108108            <!-- TODO: find an easy way to validate projections codes -->
    109109            <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>
    122110        </xs:sequence>
    123111    </xs:complexType>
    … …  
    725713                            <!-- old unused feature, ignored -->
    726714                            <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"/>
    736715                            <!-- does imagery server supports "/status" and tile re-rendering using "/dirty". Defaults to false. -->
    737716                            <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" />
    740717                        </xs:choice>
    741718                    </xs:sequence>
  • /trunk/src/org/openstreetmap/josm/actions/AddImageryLayerAction.java

    r13733 r13732  
    1313import java.util.ArrayList;
    1414import java.util.Collection;
     15import java.util.HashSet;
    1516import java.util.List;
    16 import java.util.stream.Collectors;
     17import java.util.Set;
    1718
    1819import javax.swing.JComboBox;
    … …  
    2627import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType;
    2728import org.openstreetmap.josm.data.imagery.WMTSTileSource;
    28 import org.openstreetmap.josm.data.imagery.WMTSTileSource.WMTSGetCapabilitiesException;
    2929import org.openstreetmap.josm.gui.ExtendedDialog;
    3030import org.openstreetmap.josm.gui.layer.AlignImageryPanel;
    … …  
    3434import org.openstreetmap.josm.gui.util.GuiHelper;
    3535import org.openstreetmap.josm.io.imagery.WMSImagery;
     36import org.openstreetmap.josm.io.imagery.WMSImagery.LayerDetails;
    3637import org.openstreetmap.josm.io.imagery.WMSImagery.WMSGetCapabilitiesException;
    3738import org.openstreetmap.josm.tools.CheckParameterUtil;
    … …  
    9596            case WMS_ENDPOINT:
    9697                // 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);
    10299            case WMTS:
    103100                // 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;
    116108                }
     109                // layer not selected - refuse to add
     110                return null;
    117111            default:
    118112                return info;
    … …  
    136130            }
    137131            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);
    144132        }
    145133        return null;
    … …  
    178166     */
    179167    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;
    244209    }
    245210
  • /trunk/src/org/openstreetmap/josm/data/cache/JCSCachedTileLoaderJob.java

    r13733 r13732  
    2121import org.apache.commons.jcs.engine.behavior.ICacheElement;
    2222import org.openstreetmap.josm.data.cache.ICachedLoaderListener.LoadResult;
    23 import org.openstreetmap.josm.data.imagery.TileJobOptions;
    2423import org.openstreetmap.josm.data.preferences.IntegerProperty;
    2524import org.openstreetmap.josm.tools.CheckParameterUtil;
    … …  
    9493    private Runnable finishTask;
    9594    private boolean force;
    96     private long minimumExpiryTime;
    9795
    9896    /**
    9997     * @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
    101101     * @param downloadJobExecutor that will be executing the jobs
    102102     */
    103103    public JCSCachedTileLoaderJob(ICacheAccess<K, V> cache,
    104             TileJobOptions options,
     104            int connectTimeout, int readTimeout,
     105            Map<String, String> headers,
    105106            ThreadPoolExecutor downloadJobExecutor) {
    106107        CheckParameterUtil.ensureParameterNotNull(cache, "cache");
    107108        this.cache = cache;
    108109        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;
    112113        this.downloadJobExecutor = downloadJobExecutor;
    113         this.minimumExpiryTime = TimeUnit.SECONDS.toMillis(options.getMinimumExpiryTime());
    114114    }
    115115
    116116    /**
    117117     * @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
    119121     */
    120122    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);
    123127    }
    124128
    … …  
    274278            // put a limit to the expire time (some servers send a value
    275279            // 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);
    277281            if (now > expires) {
    278282                Logging.debug("JCS - Object {0} has expired -> valid to {1}, now is: {2}",
    … …  
    281285            }
    282286        } else if (attributes.getLastModification() > 0 &&
    283                 now - attributes.getLastModification() > Math.max(DEFAULT_EXPIRE_TIME, minimumExpiryTime)) {
     287                now - attributes.getLastModification() > DEFAULT_EXPIRE_TIME) {
    284288            // check by file modification date
    285289            Logging.debug("JCS - Object has expired, maximum file age reached {0}", getUrlNoException());
    286290            return false;
    287         } else if (now - attributes.getCreateTime() > Math.max(DEFAULT_EXPIRE_TIME, minimumExpiryTime)) {
     291        } else if (now - attributes.getCreateTime() > DEFAULT_EXPIRE_TIME) {
    288292            Logging.debug("JCS - Object has expired, maximum time since object creation reached {0}", getUrlNoException());
    289293            return false;
    … …  
    326330                // and the server answers with a HTTP 304 = "Not Modified"
    327331                Logging.debug("JCS - If-Modified-Since/ETag test: local version is up to date: {0}", getUrl());
    328                 // update cache attributes
    329                 attributes = parseHeaders(urlConn);
    330                 cache.put(getCacheKey(), cacheData, attributes);
    331332                return true;
    332333            } else if (isObjectLoadable() // we have an object in cache, but we haven't received 304 response code
    … …  
    452453                Logging.trace(e);
    453454            }
    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);
    460458        ret.setLastModification(now);
    461459        ret.setEtag(urlConn.getHeaderField("ETag"));
    … …  
    482480        final HttpClient.Response urlConn = getRequest("HEAD", false).connect();
    483481        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"))) ||
    485483                (lastModified != 0 && lastModified <= attributes.getLastModification());
    486         if (ret) {
    487             // update attributes
    488             attributes = parseHeaders(urlConn);
    489             cache.put(getCacheKey(), cacheData, attributes);
    490         }
    491         return ret;
    492484    }
    493485
  • /trunk/src/org/openstreetmap/josm/data/imagery/AbstractWMSTileSource.java

    r13733 r13732  
    33
    44import java.awt.Point;
    5 import java.text.DecimalFormat;
    6 import java.text.DecimalFormatSymbols;
    7 import java.text.NumberFormat;
    8 import java.util.Locale;
    95
    106import org.openstreetmap.gui.jmapviewer.Projected;
    … …  
    2824public abstract class AbstractWMSTileSource extends TMSTileSource {
    2925
    30     static final NumberFormat LATLON_FORMAT = new DecimalFormat("###0.0000000", new DecimalFormatSymbols(Locale.US));
    31 
    3226    private EastNorth anchorPosition;
    3327    private int[] tileXMin;
    … …  
    215209        return this.tileProjection.toCode();
    216210    }
    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     }
    238211}
  • /trunk/src/org/openstreetmap/josm/data/imagery/CachedTileLoaderFactory.java

    r13733 r13732  
    4444                    TileLoaderListener.class,
    4545                    ICacheAccess.class,
    46                     TileJobOptions.class
    47                     );
     46                    int.class,
     47                    int.class,
     48                    Map.class);
    4849        } catch (NoSuchMethodException | SecurityException e) {
    4950            Logging.log(Logging.LEVEL_WARN, "Unable to initialize cache tile loader factory", e);
    … …  
    6364
    6465    @Override
    65     public TileLoader makeTileLoader(TileLoaderListener listener, Map<String, String> inputHeaders, long minimumExpiryTime) {
     66    public TileLoader makeTileLoader(TileLoaderListener listener, Map<String, String> inputHeaders) {
    6667        Map<String, String> headers = new ConcurrentHashMap<>();
    6768        headers.put("User-Agent", Version.getInstance().getFullAgentString());
    … …  
    7172
    7273        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);
    8077    }
    8178
    8279    protected TileLoader getLoader(TileLoaderListener listener, ICacheAccess<String, BufferedImageCacheEntry> cache,
    83             TileJobOptions options) {
     80            int connectTimeout, int readTimeout, Map<String, String> headers) {
    8481        try {
    8582            return tileLoaderConstructor.newInstance(
    8683                    listener,
    8784                    cache,
    88                     options
    89                     );
     85                    connectTimeout,
     86                    readTimeout,
     87                    headers);
    9088        } catch (IllegalArgumentException e) {
    9189            Logging.warn(e);
  • /trunk/src/org/openstreetmap/josm/data/imagery/DefaultLayer.java

    r13733 r13732  
    11// License: GPL. For details, see LICENSE file.
    22package 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;
    113
    124/**
    … …  
    2012 */
    2113public class DefaultLayer {
    22     private final String layerName;
    23     private final String tileMatrixSet;
    24     private final String style;
     14
     15    protected String layerName;
    2516
    2617    /**
    2718     * 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
    3220     */
    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;
    4023    }
    4124
    … …  
    4730    }
    4831
    49     /**
    50      * @return default tileMatrixSet. Only usable for WMTS
    51      */
    52     public String getTileMatrixSet() {
    53         return tileMatrixSet;
    54     }
    55 
    56     /**
    57      * @return style for this WMS / WMTS layer to use
    58      */
    59     public String getStyle() {
    60         return style;
    61     }
    62 
    63     /**
    64      * @return JSON representation of the default layer object
    65      */
    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 objects
    76      * @param o serialized DefaultLayer object
    77      * @param type of ImageryType serialized
    78      * @return DefaultLayer instance based on JSON object
    79      */
    80     public static DefaultLayer fromJson(JsonObject o, ImageryType type) {
    81         return new DefaultLayer(type, o.getString("layerName"), o.getString("style"), o.getString("tileMatrixSet"));
    82     }
    8332}
  • /trunk/src/org/openstreetmap/josm/data/imagery/ImageryInfo.java

    r13733 r13732  
    55
    66import java.awt.Image;
    7 import java.io.StringReader;
    87import java.util.ArrayList;
    98import java.util.Arrays;
    … …  
    1615import java.util.Set;
    1716import java.util.TreeSet;
    18 import java.util.concurrent.ConcurrentHashMap;
    19 import java.util.concurrent.TimeUnit;
    2017import java.util.regex.Matcher;
    2118import java.util.regex.Pattern;
    2219import java.util.stream.Collectors;
    2320
    24 import javax.json.Json;
    25 import javax.json.JsonObject;
    26 import javax.json.stream.JsonCollectors;
    2721import javax.swing.ImageIcon;
    2822
    … …  
    224218    private boolean isGeoreferenceValid;
    225219    /** 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
    239223
    240224    /**
    … …  
    274258        @StructEntry boolean modTileFeatures;
    275259        @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;
    280262
    281263        /**
    … …  
    326308                }
    327309            }
    328             if (!i.serverProjections.isEmpty()) {
    329                 projections = i.serverProjections.stream().collect(Collectors.joining(","));
    330             }
     310            projections = i.serverProjections.stream().collect(Collectors.joining(","));
    331311            if (i.noTileHeaders != null && !i.noTileHeaders.isEmpty()) {
    332312                noTileHeaders = new MultiMap<>(i.noTileHeaders);
    … …  
    345325            valid_georeference = i.isGeoreferenceValid();
    346326            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(","));
    353329        }
    354330
    … …  
    492468        isGeoreferenceValid = e.valid_georeference;
    493469        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(","));
    504472    }
    505473
    … …  
    546514        this.isGeoreferenceValid = i.isGeoreferenceValid;
    547515        this.defaultLayers = i.defaultLayers;
    548         this.customHttpHeaders = i.customHttpHeaders;
    549         this.transparent = i.transparent;
    550         this.minimumTileExpire = i.minimumTileExpire;
    551516    }
    552517
    … …  
    600565                Objects.equals(this.noTileChecksums, other.noTileChecksums) &&
    601566                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);
    606568        // CHECKSTYLE.ON: BooleanExpressionComplexity
    607569    }
    … …  
    14101372     * @return Collection of the layer names
    14111373     */
    1412     public List<DefaultLayer> getDefaultLayers() {
     1374    public Collection<DefaultLayer> getDefaultLayers() {
    14131375        return defaultLayers;
    14141376    }
    … …  
    14181380     * @param layers set the list of default layers
    14191381     */
    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        }
    14211388        this.defaultLayers = layers;
    14221389    }
    1423 
    1424     /**
    1425      * Returns custom HTTP headers that should be sent with request towards imagery provider
    1426      * @return headers
    1427      */
    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 provider
    1434      * @param customHttpHeaders
    1435      */
    1436     public void setCustomHttpHeaders(Map<String, String> customHttpHeaders) {
    1437         this.customHttpHeaders = customHttpHeaders;
    1438     }
    1439 
    1440     /**
    1441      * @return should this imagery be transparent
    1442      */
    1443     public boolean isTransparent() {
    1444         return transparent;
    1445     }
    1446 
    1447     /**
    1448      *
    1449      * @param transparent set to true if imagery should be transparent
    1450      */
    1451     public void setTransparent(boolean transparent) {
    1452         this.transparent = transparent;
    1453     }
    1454 
    1455     /**
    1456      * @return minimum tile expiration in seconds
    1457      */
    1458     public int getMinimumTileExpire() {
    1459         return minimumTileExpire;
    1460     }
    1461 
    1462     /**
    1463      * Sets minimum tile expiration in seconds
    1464      * @param minimumTileExpire
    1465      */
    1466     public void setMinimumTileExpire(int minimumTileExpire) {
    1467         this.minimumTileExpire = minimumTileExpire;
    1468 
    1469     }
    14701390}
  • /trunk/src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoader.java

    r13733 r13732  
    22package org.openstreetmap.josm.data.imagery;
    33
     4import java.util.Map;
    45import java.util.concurrent.ThreadPoolExecutor;
    56import java.util.concurrent.TimeUnit;
    … …  
    2627
    2728    protected final ICacheAccess<String, BufferedImageCacheEntry> cache;
     29    protected final int connectTimeout;
     30    protected final int readTimeout;
     31    protected final Map<String, String> headers;
    2832    protected final TileLoaderListener listener;
    2933
    … …  
    4650
    4751    private ThreadPoolExecutor downloadExecutor = DEFAULT_DOWNLOAD_JOB_DISPATCHER;
    48     protected final TileJobOptions options;
    4952
    5053    /**
    5154     * Constructor
    5255     * @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
    5560     */
    5661    public TMSCachedTileLoader(TileLoaderListener listener, ICacheAccess<String, BufferedImageCacheEntry> cache,
    57            TileJobOptions options) {
     62            int connectTimeout, int readTimeout, Map<String, String> headers) {
    5863        CheckParameterUtil.ensureParameterNotNull(cache, "cache");
    5964        this.cache = cache;
    60         this.options = options;
     65        this.connectTimeout = connectTimeout;
     66        this.readTimeout = readTimeout;
     67        this.headers = headers;
    6168        this.listener = listener;
    6269    }
    … …  
    9198    @Override
    9299    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());
    99102    }
    100103
  • /trunk/src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoaderJob.java

    r13733 r13732  
    4444 */
    4545public 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));
    5048    static final Pattern SERVICE_EXCEPTION_PATTERN = Pattern.compile("(?s).+<ServiceException[^>]*>(.+)</ServiceException>.+");
    5149    protected final Tile tile;
    5250    private volatile URL url;
    53     private final TileJobOptions options;
    5451
    5552    // we need another deduplication of Tile Loader listeners, as for each submit, new TMSCachedTileLoaderJob was created
    … …  
    6259     * @param tile to be fetched from cache
    6360     * @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
    6564     * @param downloadExecutor that will be executing the jobs
    6665     */
    6766    public TMSCachedTileLoaderJob(TileLoaderListener listener, Tile tile,
    6867            ICacheAccess<String, BufferedImageCacheEntry> cache,
    69             TileJobOptions options,
     68            int connectTimeout, int readTimeout, Map<String, String> headers,
    7069            ThreadPoolExecutor downloadExecutor) {
    71         super(cache, options, downloadExecutor);
     70        super(cache, connectTimeout, readTimeout, headers, downloadExecutor);
    7271        this.tile = tile;
    73         this.options = options;
    7472        if (listener != null) {
    7573            String deduplicationKey = getCacheKey();
    … …  
    247245        // keep the expiration time between MINIMUM_EXPIRES and MAXIMUM_EXPIRES, so we will cache the tiles
    248246        // at least for some short period of time, but not too long
    249         if (ret.getExpirationTime() < now + Math.max(MINIMUM_EXPIRES.get(), options.getMinimumExpiryTime())) {
     247        if (ret.getExpirationTime() < now + MINIMUM_EXPIRES.get()) {
    250248            ret.setExpirationTime(now + MINIMUM_EXPIRES.get());
    251249        }
    252         if (ret.getExpirationTime() > now + Math.max(MAXIMUM_EXPIRES.get(), options.getMinimumExpiryTime())) {
     250        if (ret.getExpirationTime() > now + MAXIMUM_EXPIRES.get()) {
    253251            ret.setExpirationTime(now + MAXIMUM_EXPIRES.get());
    254252        }
  • /trunk/src/org/openstreetmap/josm/data/imagery/TemplatedWMSTileSource.java

    r13733 r13732  
    5959        super(info, tileProjection);
    6060        this.serverProjections = new TreeSet<>(info.getServerProjections());
    61         this.headers.putAll(info.getCustomHttpHeaders());
    6261        handleTemplate();
    6362        initProjection();
    … …  
    110109            switchLatLon = Main.getProjection().switchXY();
    111110        }
    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        }
    113119
    114120        // Using StringBuffer and generic PATTERN_PARAM matcher gives 2x performance improvement over replaceAll
  • /trunk/src/org/openstreetmap/josm/data/imagery/TileLoaderFactory.java

    r13733 r13732  
    1919     * @param listener that will be notified, when tile has finished loading
    2020     * @param headers that will be sent with requests to TileSource. <code>null</code> indicates none
    21      * @param minimumExpiryTime minimum expiry time
    2221     * @return TileLoader that uses both of above
    2322     */
    24     TileLoader makeTileLoader(TileLoaderListener listener, Map<String, String> headers, long minimumExpiryTime);
     23    TileLoader makeTileLoader(TileLoaderListener listener, Map<String, String> headers);
    2524}
  • /trunk/src/org/openstreetmap/josm/data/imagery/WMSCachedTileLoader.java

    r13733 r13732  
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.data.imagery;
     3
     4import java.util.Map;
    35
    46import org.apache.commons.jcs.access.behavior.ICacheAccess;
    … …  
    3335     */
    3436    public WMSCachedTileLoader(TileLoaderListener listener, ICacheAccess<String, BufferedImageCacheEntry> cache,
    35             TileJobOptions options) {
     37            int connectTimeout, int readTimeout, Map<String, String> headers) {
    3638
    37         super(listener, cache, options);
     39        super(listener, cache, connectTimeout, readTimeout, headers);
    3840        setDownloadExecutor(TMSCachedTileLoader.getNewThreadPoolExecutor("WMS-downloader-%d", THREAD_LIMIT.get()));
    3941    }
    … …  
    4143    @Override
    4244    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());
    4446    }
    4547}
  • /trunk/src/org/openstreetmap/josm/data/imagery/WMSCachedTileLoaderJob.java

    r13733 r13732  
    22package org.openstreetmap.josm.data.imagery;
    33
     4import java.util.Map;
    45import java.util.concurrent.ThreadPoolExecutor;
    56
    … …  
    2223     * @param tile to load
    2324     * @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
    2528     * @param downloadExecutor that will execute the download task (if needed)
    2629     */
    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);
    3334    }
    3435
  • /trunk/src/org/openstreetmap/josm/data/imagery/WMTSTileSource.java

    r13733 r13732  
    5151import org.openstreetmap.josm.data.coor.EastNorth;
    5252import org.openstreetmap.josm.data.coor.LatLon;
    53 import org.openstreetmap.josm.data.imagery.GetCapabilitiesParseHelper.TransferMode;
    54 import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType;
    5553import org.openstreetmap.josm.data.projection.Projection;
    5654import org.openstreetmap.josm.data.projection.Projections;
    … …  
    128126    }
    129127
    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 {
    137129
    138130        private final List<TileMatrix> tileMatrix;
    … …  
    162154            return "TileMatrixSet [crs=" + crs + ", identifier=" + identifier + ']';
    163155        }
    164 
    165         /**
    166          *
    167          * @return identifier of this TileMatrixSet
    168          */
    169         public String getIdentifier() {
    170             return identifier;
    171         }
    172156    }
    173157
    … …  
    178162    }
    179163
    180     /**
    181      * Class representing WMTS Layer information
    182      *
    183      */
    184     public static class Layer {
     164    private static class Layer {
    185165        private String format;
    186166        private String identifier;
    … …  
    222202                    + tileMatrixSet + ", baseUrl=" + baseUrl + ", style=" + style + ']';
    223203        }
    224 
    225         /**
    226          *
    227          * @return identifier of this layer
    228          */
    229         public String getIdentifier() {
    230             return identifier;
    231         }
    232 
    233         /**
    234          *
    235          * @return style of this layer
    236          */
    237         public String getStyle() {
    238             return style;
    239         }
    240 
    241         /**
    242          *
    243          * @return
    244          */
    245         public TileMatrixSet getTileMatrixSet() {
    246             return tileMatrixSet;
    247         }
    248     }
    249 
    250     /**
    251      * Exception thrown when praser doesn't find expected information in GetCapabilities document
    252      *
    253      */
    254     public static class WMTSGetCapabilitiesException extends Exception {
    255 
    256         /**
    257          * @param cause description of cause
    258          */
    259         public WMTSGetCapabilitiesException(String cause) {
    260             super(cause);
    261         }
    262204    }
    263205
    … …  
    269211            super(Main.parent, tr("Select WMTS layer"), tr("Add layers"), tr("Cancel"));
    270212            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);
    272264            JPanel panel = new JPanel(new GridBagLayout());
    273265            panel.add(new JScrollPane(this.list), GBC.eol().fill());
    … …  
    281273            }
    282274            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        }
    286283    }
    287284
    … …  
    295292    private ScaleList nativeScaleList;
    296293
    297     private final DefaultLayer defaultLayer;
     294    private final WMTSDefaultLayer defaultLayer;
    298295
    299296    private Projection tileProjection;
    … …  
    303300     * @param info imagery info
    304301     * @throws IOException if any I/O error occurs
    305      * @throws WMTSGetCapabilitiesException
    306302     * @throws IllegalArgumentException if any other error happens for the given imagery info
    307303     */
    308     public WMTSTileSource(ImageryInfo info) throws IOException, WMTSGetCapabilitiesException {
     304    public WMTSTileSource(ImageryInfo info) throws IOException {
    309305        super(info);
    310306        CheckParameterUtil.ensureThat(info.getDefaultLayers().size() < 2, "At most 1 default layer for WMTS is supported");
    311         this.headers.putAll(info.getCustomHttpHeaders());
     307
    312308        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();
    317310        if (info.getDefaultLayers().isEmpty()) {
    318311            Logging.warn(tr("No default layer selected, choosing first layer."));
    319312            if (!layers.isEmpty()) {
    320313                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);
    322315            } else {
    323316                this.defaultLayer = null;
    324317            }
    325318        } 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            }
    327325        }
    328326        if (this.layers.isEmpty())
    … …  
    345343                // only one tile matrix set with matching projection - no point in asking
    346344                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);
    348346            }
    349347        }
    … …  
    368366    }
    369367
    370 
    371     /**
    372      * @param url of the getCapabilities document
    373      * @param headers HTTP headers to set when calling getCapabilities url
     368    /**
    374369     * @return capabilities
    375370     * @throws IOException in case of any I/O error
    376      * @throws WMTSGetCapabilitiesException
    377371     * @throws IllegalArgumentException in case of any other error
    378372     */
    379     public static WMTSCapabilities getCapabilities(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).
    381375                setMaxAge(Config.getPref().getLong("wmts.capabilities.cache.max_age", 7 * CachedFile.DAYS)).
    382376                setCachingStrategy(CachedFile.CachingStrategy.IfModifiedSince).
    … …  
    385379            if (data.length == 0) {
    386380                cf.clear();
    387                 throw new IllegalArgumentException("Could not read data from: " + url);
     381                throw new IllegalArgumentException("Could not read data from: " + baseUrl);
    388382            }
    389383
    390384            try {
    391385                XMLStreamReader reader = GetCapabilitiesParseHelper.getReader(new ByteArrayInputStream(data));
    392                 WMTSCapabilities ret = null;
    393                 Collection<Layer> layers = null;
     386                Collection<Layer> ret = new ArrayList<>();
    394387                for (int event = reader.getEventType(); reader.hasNext(); event = reader.next()) {
    395388                    if (event == XMLStreamReader.START_ELEMENT) {
    396389                        if (GetCapabilitiesParseHelper.QN_OWS_OPERATIONS_METADATA.equals(reader.getName())) {
    397                             ret = parseOperationMetadata(reader);
     390                            parseOperationMetadata(reader);
    398391                        }
    399392
    400393                        if (QN_CONTENTS.equals(reader.getName())) {
    401                             layers = parseContents(reader);
     394                            ret = parseContents(reader);
    402395                        }
    403396                    }
    404397                }
    405                 if (ret == null) {
    406                     /*
    407                      *  see #12168 - create dummy operation metadata - not all WMTS services provide this information
    408                      *
    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 justifiable
    413                      */
    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);
    420398                return ret;
    421399            } catch (XMLStreamException e) {
    … …  
    478456        supportedMimeTypes.add("image/jpgpng");         // used by ESRI
    479457        supportedMimeTypes.add("image/png8");           // used by geoserver
    480         if (supportedMimeTypes.contains("image/jpeg")) {
    481             supportedMimeTypes.add("image/jpg"); // sometimes mispelled by Arcgis
    482         }
    483458        Collection<String> unsupportedFormats = new ArrayList<>();
    484459
    … …  
    664639    /**
    665640     * Parses OperationMetadata section. Returns when reader is on OperationsMetadata closing tag.
    666      * return WMTSCapabilities with baseUrl and transferMode
     641     * Sets this.baseUrl and this.transferMode
    667642     *
    668643     * @param reader StAX reader instance
    669      * @return WMTSCapabilities with baseUrl and transferMode set
    670644     * @throws XMLStreamException See {@link XMLStreamReader}
    671645     */
    672     private static WMTSCapabilities parseOperationMetadata(XMLStreamReader reader) throws XMLStreamException {
     646    private void parseOperationMetadata(XMLStreamReader reader) throws XMLStreamException {
    673647        for (int event = reader.getEventType();
    674648                reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT &&
    … …  
    683657                            GetCapabilitiesParseHelper.QN_OWS_GET
    684658                    )) {
    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        }
    692663    }
    693664
    … …  
    700671            return;
    701672        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()))
    703674                .collect(Collectors.toList());
    704675        if (matchingLayers.size() > 1) {
    … …  
    715686                this.tileProjection = null;
    716687                for (Layer layer : layers) {
    717                     if (!layer.identifier.equals(defaultLayer.getLayerName())) {
     688                    if (!layer.identifier.equals(defaultLayer.layerName)) {
    718689                        continue;
    719690                    }
    … …  
    951922    }
    952923
    953     public static JTable getLayerSelectionPanel(List<Entry<String, List<Layer>>> layers) {
    954         JTable list = new JTable(
    955                 new AbstractTableModel() {
    956                     @Override
    957                     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 one
    964                         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 one
    974                         default:
    975                             throw new IllegalArgumentException();
    976                         }
    977                     }
    978 
    979                     @Override
    980                     public int getRowCount() {
    981                         return layers.size();
    982                     }
    983 
    984                     @Override
    985                     public int getColumnCount() {
    986                         return 3;
    987                     }
    988 
    989                     @Override
    990                     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 
    1014924    /**
    1015925     * @return set of projection codes that this TileSource supports
  • /trunk/src/org/openstreetmap/josm/gui/bbox/SlippyMapBBoxChooser.java

    r13733 r13732  
    2121import java.util.Set;
    2222import java.util.concurrent.CopyOnWriteArrayList;
    23 import java.util.concurrent.TimeUnit;
    2423
    2524import javax.swing.ButtonModel;
    … …  
    159158        TileLoaderFactory cachedLoaderFactory = AbstractCachedTileSourceLayer.getTileLoaderFactory("TMS", TMSCachedTileLoader.class);
    160159        if (cachedLoaderFactory != null) {
    161             cachedLoader = cachedLoaderFactory.makeTileLoader(this, headers, TimeUnit.HOURS.toSeconds(1));
     160            cachedLoader = cachedLoaderFactory.makeTileLoader(this, headers);
    162161        } else {
    163162            cachedLoader = null;
  • /trunk/src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java

    r13733 r13732  
    203203    // prepared to be moved to the painter
    204204    protected TileCoordinateConverter coordinateConverter;
    205     private final long minimumTileExpire;
    206205
    207206    /**
    … …  
    215214        getFilterSettings().addFilterChangeListener(this);
    216215        getDisplaySettings().addSettingsChangeListener(this);
    217         this.minimumTileExpire = info.getMinimumTileExpire();
    218216    }
    219217
    … …  
    276274        Map<String, String> headers = getHeaders(tileSource);
    277275
    278         tileLoader = getTileLoaderFactory().makeTileLoader(this, headers, minimumTileExpire);
     276        tileLoader = getTileLoaderFactory().makeTileLoader(this, headers);
    279277
    280278        try {
    … …  
    17611759        public PrecacheTask(ProgressMonitor progressMonitor) {
    17621760            this.progressMonitor = progressMonitor;
    1763             this.tileLoader = getTileLoaderFactory().makeTileLoader(this, getHeaders(tileSource), minimumTileExpire);
     1761            this.tileLoader = getTileLoaderFactory().makeTileLoader(this, getHeaders(tileSource));
    17641762            if (this.tileLoader instanceof TMSCachedTileLoader) {
    17651763                ((TMSCachedTileLoader) this.tileLoader).setDownloadExecutor(
  • /trunk/src/org/openstreetmap/josm/gui/layer/ImageryLayer.java

    r13733 r13732  
    196196        switch(info.getImageryType()) {
    197197        case WMS:
    198         case WMS_ENDPOINT:
    199198            return new WMSLayer(info);
    200199        case WMTS:
  • /trunk/src/org/openstreetmap/josm/gui/layer/WMSLayer.java

    r13733 r13732  
    2525import org.openstreetmap.josm.data.imagery.TemplatedWMSTileSource;
    2626import org.openstreetmap.josm.data.imagery.WMSCachedTileLoader;
    27 import org.openstreetmap.josm.data.imagery.WMSEndpointTileSource;
    2827import org.openstreetmap.josm.data.preferences.BooleanProperty;
    2928import org.openstreetmap.josm.data.preferences.IntegerProperty;
    … …  
    5857    private static final String CACHE_REGION_NAME = "WMS";
    5958
    60     private List<String> serverProjections;
     59    private final List<String> serverProjections;
    6160
    6261    /**
    … …  
    6665    public WMSLayer(ImageryInfo info) {
    6766        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");
    6968        CheckParameterUtil.ensureParameterNotNull(info.getUrl(), "info.url");
    70         if (info.getImageryType() == ImageryType.WMS) {
    71             TemplatedWMSTileSource.checkUrl(info.getUrl());
    72 
    73         }
     69        TemplatedWMSTileSource.checkUrl(info.getUrl());
    7470        this.serverProjections = new ArrayList<>(info.getServerProjections());
    7571    }
    … …  
    9389    @Override
    9490    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()));
    11393        info.setAttribution(tileSource);
    11494        return tileSource;
  • /trunk/src/org/openstreetmap/josm/gui/layer/WMTSLayer.java

    r13733 r13732  
    1313import org.openstreetmap.josm.data.imagery.WMSCachedTileLoader;
    1414import org.openstreetmap.josm.data.imagery.WMTSTileSource;
    15 import org.openstreetmap.josm.data.imagery.WMTSTileSource.WMTSGetCapabilitiesException;
    1615import org.openstreetmap.josm.data.projection.Projection;
    1716import org.openstreetmap.josm.gui.MainApplication;
    … …  
    6564            }
    6665            return null;
    67         } catch (IOException | WMTSGetCapabilitiesException e) {
     66        } catch (IOException e) {
    6867            Logging.warn(e);
    6968            throw new IllegalArgumentException(e);
  • /trunk/src/org/openstreetmap/josm/io/imagery/ImageryReader.java

    r13733 r13732  
    77import java.util.ArrayList;
    88import java.util.Arrays;
     9import java.util.HashMap;
    910import java.util.List;
    1011import java.util.Map;
    1112import java.util.Objects;
    1213import java.util.Stack;
    13 import java.util.concurrent.ConcurrentHashMap;
    1414
    1515import javax.xml.parsers.ParserConfigurationException;
    1616
    17 import org.openstreetmap.josm.data.imagery.DefaultLayer;
    1817import org.openstreetmap.josm.data.imagery.ImageryInfo;
    1918import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryBounds;
    … …  
    5857        NO_TILESUM,
    5958        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
    6460    }
    6561
    … …  
    136132        private MultiMap<String, String> noTileChecksums;
    137133        private Map<String, String> metadataHeaders;
    138         private List<DefaultLayer> defaultLayers;
    139         private Map<String, String> customHttpHeaders;
    140134
    141135        @Override
    … …  
    151145            noTileHeaders = null;
    152146            noTileChecksums = null;
    153             customHttpHeaders = null;
    154147        }
    155148
    … …  
    171164                    noTileHeaders = new MultiMap<>();
    172165                    noTileChecksums = new MultiMap<>();
    173                     metadataHeaders = new ConcurrentHashMap<>();
    174                     defaultLayers = new ArrayList<>();
    175                     customHttpHeaders = new ConcurrentHashMap<>();
     166                    metadataHeaders = new HashMap<>();
    176167                    String best = atts.getValue("eli-best");
    177168                    if (TRUE.equals(best)) {
    … …  
    224215                        TILE_SIZE,
    225216                        "valid-georeference",
    226                         "mod-tile-features",
    227                         "transparent",
    228                         "minimum-tile-expire"
     217                        "mod-tile-features"
    229218                ).contains(qName)) {
    230219                    newState = State.ENTRY_ATTRIBUTE;
    … …  
    258247                    metadataHeaders.put(atts.getValue("header-name"), atts.getValue("metadata-key"));
    259248                    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;
    265249                }
    266250                break;
    … …  
    285269                if ("code".equals(qName)) {
    286270                    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")));
    293271                }
    294272                break;
    … …  
    328306                    entry.setMetadataHeaders(metadataHeaders);
    329307                    metadataHeaders = null;
    330                     entry.setDefaultLayers(defaultLayers);
    331                     defaultLayers = null;
    332                     entry.setCustomHttpHeaders(customHttpHeaders);
    333                     customHttpHeaders = null;
    334308
    335309                    if (!skipEntry) {
    … …  
    349323                    switch(qName) {
    350324                    case "type":
    351                         ImageryType.values();
    352325                        boolean found = false;
    353326                        for (ImageryType type : ImageryType.values()) {
    … …  
    514487                case "mod-tile-features":
    515488                    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()));
    522489                    break;
    523490                default: // Do nothing
  • /trunk/src/org/openstreetmap/josm/io/imagery/WMSImagery.java

    r13733 r13732  
    22package org.openstreetmap.josm.io.imagery;
    33
    4 import static java.nio.charset.StandardCharsets.UTF_8;
    5 import static org.openstreetmap.josm.tools.I18n.tr;
    6 
    7 import java.io.File;
     4import java.awt.HeadlessException;
    85import java.io.IOException;
    96import java.io.InputStream;
     7import java.io.StringReader;
     8import java.io.StringWriter;
    109import java.net.MalformedURLException;
    1110import java.net.URL;
    … …  
    1413import java.util.Collections;
    1514import java.util.HashSet;
     15import java.util.Iterator;
    1616import java.util.List;
    17 import java.util.Map;
     17import java.util.Locale;
     18import java.util.NoSuchElementException;
    1819import java.util.Set;
    19 import java.util.concurrent.ConcurrentHashMap;
    20 import java.util.function.Function;
     20import java.util.regex.Matcher;
    2121import java.util.regex.Pattern;
    2222import java.util.stream.Collectors;
     23import java.util.stream.Stream;
     24import java.util.stream.StreamSupport;
    2325
    2426import javax.imageio.ImageIO;
    25 import javax.xml.namespace.QName;
    26 import javax.xml.stream.XMLStreamException;
    27 import javax.xml.stream.XMLStreamReader;
     27import javax.xml.parsers.DocumentBuilder;
     28import javax.xml.parsers.ParserConfigurationException;
     29import javax.xml.transform.TransformerException;
     30import javax.xml.transform.TransformerFactory;
     31import javax.xml.transform.TransformerFactoryConfigurationError;
     32import javax.xml.transform.dom.DOMSource;
     33import javax.xml.transform.stream.StreamResult;
    2834
    2935import org.openstreetmap.josm.data.Bounds;
    30 import org.openstreetmap.josm.data.coor.EastNorth;
    31 import org.openstreetmap.josm.data.imagery.DefaultLayer;
    3236import org.openstreetmap.josm.data.imagery.GetCapabilitiesParseHelper;
    3337import org.openstreetmap.josm.data.imagery.ImageryInfo;
    34 import org.openstreetmap.josm.data.imagery.LayerDetails;
    35 import org.openstreetmap.josm.data.projection.Projection;
    3638import org.openstreetmap.josm.data.projection.Projections;
    37 import org.openstreetmap.josm.io.CachedFile;
     39import org.openstreetmap.josm.tools.HttpClient;
     40import org.openstreetmap.josm.tools.HttpClient.Response;
    3841import org.openstreetmap.josm.tools.Logging;
    3942import org.openstreetmap.josm.tools.Utils;
     43import org.w3c.dom.Document;
     44import org.w3c.dom.Element;
     45import org.w3c.dom.Node;
     46import org.w3c.dom.NodeList;
     47import org.xml.sax.InputSource;
     48import org.xml.sax.SAXException;
    4049
    4150/**
    … …  
    4453public class WMSImagery {
    4554
    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    }
    8585
    8686    /**
    … …  
    120120    }
    121121
    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
    212130     */
    213131    public List<LayerDetails> getLayers() {
    … …  
    216134
    217135    /**
     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    /**
    218153     * Returns the list of supported formats.
    219154     * @return the list of supported formats
    220155     */
    221     public Collection<String> getFormats() {
     156    public List<String> getFormats() {
    222157        return Collections.unmodifiableList(formats);
    223158    }
    224159
    225160    /**
    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")) {
    231168            return "image/png";
    232         } else if (formats.contains("image/jpeg")) {
    233             return "image/jpeg";
    234169        } else if (formats.isEmpty()) {
    235170            return null;
    … …  
    239174    }
    240175
    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) {
    246178            return null;
    247179        }
    248         if (getMapUrl != null) {
    249             return getMapUrl;
    250         }
    251 
    252         URL serviceUrl = capabilitiesUrl;
    253180        StringBuilder a = new StringBuilder(serviceUrl.getProtocol());
    254181        a.append("://").append(serviceUrl.getHost());
    … …  
    267194
    268195    /**
    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();
    352319
    353320            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;
    374356                    }
    375357                }
    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        }
    615366    }
    616367
    … …  
    641392    }
    642393
    643 
    644394    static boolean imageFormatHasTransparency(final String format) {
    645395        return format != null && (format.startsWith("image/png") || format.startsWith("image/gif")
    … …  
    648398
    649399    /**
    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);
    662413        }
    663414        return i;
    664415    }
    665416
    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        }
    704615    }
    705616}
  • /trunk/test/unit/org/openstreetmap/josm/TestUtils.java

    r13733 r13732  
    1515import java.security.AccessController;
    1616import 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;
    2117import java.util.Arrays;
    2218import java.util.Collection;
    … …  
    4238import org.openstreetmap.josm.tools.JosmRuntimeException;
    4339import org.openstreetmap.josm.tools.Utils;
    44 
    45 import com.github.tomakehurst.wiremock.WireMockServer;
    46 import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
    4740
    4841import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
    … …  
    387380        }
    388381    }
    389 
    390     /**
    391      * Return WireMock server serving files under ticker directory
    392      * @param ticketId Ticket numeric identifier
    393      * @return WireMock HTTP server on dynamic port
    394      */
    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 directory
    405      * @return WireMock HTTP server on dynamic port
    406      */
    407     public static WireMockServer getWireMockServer() {
    408             return new WireMockServer(
    409                     WireMockConfiguration.options()
    410                         .dynamicPort()
    411                     );
    412     }
    413     /**
    414      * Renders Temporal to RFC 1123 Date Time
    415      * @param time
    416      * @return string representation according to RFC1123 of time
    417      */
    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 Time
    424      * @param time
    425      * @return string representation according to RFC1123 of time
    426      */
    427     public static String getHTTPDate(long time) {
    428         return getHTTPDate(Instant.ofEpochMilli(time));
    429     }
    430 
    431 
    432382}
  • /trunk/test/unit/org/openstreetmap/josm/actions/AddImageryLayerActionTest.java

    r13733 r13732  
    7070    @Test
    7171    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"))
    7373                .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")));
    8477        new AddImageryLayerAction(new ImageryInfo("localhost", "http://localhost:" + wireMockRule.port() + "/wms?",
    8578                "wms_endpoint", null, null)).actionPerformed(null);
  • /trunk/test/unit/org/openstreetmap/josm/data/cache/HostLimitQueueTest.java

    r13733 r13732  
    1414import org.junit.Rule;
    1515import org.junit.Test;
    16 import org.openstreetmap.josm.data.imagery.TileJobOptions;
    1716import org.openstreetmap.josm.testutils.JOSMTestRules;
    1817import org.openstreetmap.josm.tools.Logging;
    … …  
    5554
    5655        Task(ICacheAccess<String, CacheEntry> cache, URL url, AtomicInteger counter) {
    57             super(cache, new TileJobOptions(1, 1, null, 10));
     56            super(cache, 1, 1, null);
    5857            this.url = url;
    5958            this.counter = counter;
  • /trunk/test/unit/org/openstreetmap/josm/data/cache/JCSCachedTileLoaderJobTest.java

    r13733 r13732  
    22package org.openstreetmap.josm.data.cache;
    33
    4 import static org.junit.Assert.assertArrayEquals;
    54import static org.junit.Assert.assertEquals;
    65import static org.junit.Assert.assertFalse;
    7 import static org.junit.Assert.assertTrue;
    86
    97import java.io.IOException;
    … …  
    119import java.net.URL;
    1210import java.nio.charset.StandardCharsets;
    13 import java.util.concurrent.TimeUnit;
    1411
    1512import org.apache.commons.jcs.access.behavior.ICacheAccess;
    16 import org.apache.commons.jcs.engine.behavior.ICacheElement;
    1713import org.junit.Before;
    1814import org.junit.Rule;
    1915import org.junit.Test;
    20 import org.openstreetmap.josm.TestUtils;
    2116import org.openstreetmap.josm.data.cache.ICachedLoaderListener.LoadResult;
    22 import org.openstreetmap.josm.data.imagery.TileJobOptions;
    2317import org.openstreetmap.josm.testutils.JOSMTestRules;
    2418import 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;
    3019
    3120import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
    … …  
    3625public class JCSCachedTileLoaderJobTest {
    3726
    38     /**
    39      * mocked tile server
    40      */
    41     @Rule
    42     public WireMockRule tileServer = new WireMockRule(WireMockConfiguration.options()
    43             .dynamicPort());
    44 
    4527    private static class TestCachedTileLoaderJob extends JCSCachedTileLoaderJob<String, CacheEntry> {
    4628        private String url;
    4729        private String key;
    4830
    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);
    5533
    5634            this.url = url;
    5735            this.key = key;
    5836        }
    59 
    6037
    6138        @Override
    … …  
    7552        @Override
    7653        protected CacheEntry createCacheEntry(byte[] content) {
    77             return new CacheEntry(content);
     54            return new CacheEntry("dummy".getBytes(StandardCharsets.UTF_8));
    7855        }
    7956    }
    … …  
    8360        private boolean ready;
    8461        private LoadResult result;
    85         private byte[] data;
    8662
    8763        @Override
    … …  
    9066            this.ready = true;
    9167            this.result = result;
    92             if (data != null) {
    93                 this.data = data.content;
    94             }
    9568            this.notifyAll();
    9669        }
    … …  
    140113        String key = "key_unknown_host";
    141114        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        }
    143127        assertEquals(LoadResult.FAILURE, listener.result); // because response will be cached, and that is checked below
    144128        assertEquals("java.net.UnknownHostException: unkownhost.unkownhost", listener.attributes.getErrorMessage());
    … …  
    151135
    152136        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        }
    154149        assertEquals(LoadResult.SUCCESS, listener.result);
    155150        assertFalse(job.isCacheElementValid());
    156151    }
    157152
    158     private void doTestStatusCode(int responseCode) throws IOException {
     153    @SuppressFBWarnings(value = "WA_NOT_IN_LOOP")
     154    private void doTestStatusCode(int responseCode) throws IOException, InterruptedException {
    159155        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        }
    161163        assertEquals(responseCode, listener.attributes.getResponseCode());
    162164    }
    163165
    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 {
    473167        return new TestCachedTileLoaderJob("http://httpstat.us/" + responseCode, "key_" + responseCode);
    474168    }
    475169
    476     private static ICacheAccess<String, CacheEntry> getCache() {
     170    private static ICacheAccess<String, CacheEntry> getCache() throws IOException {
    477171        return JCSCacheManager.getCache("test");
    478172    }
  • /trunk/test/unit/org/openstreetmap/josm/data/imagery/TMSCachedTileLoaderJobTest.java

    r13733 r13732  
    22package org.openstreetmap.josm.data.imagery;
    33
    4 import static org.junit.Assert.assertArrayEquals;
    54import static org.junit.Assert.assertEquals;
    65import static org.junit.Assert.assertTrue;
    76
    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;
    157import java.util.regex.Matcher;
    168
    17 import org.apache.commons.jcs.access.behavior.ICacheAccess;
    18 import org.junit.Before;
    199import org.junit.Rule;
    2010import 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;
    2811import org.openstreetmap.josm.testutils.JOSMTestRules;
    29 import org.openstreetmap.josm.tools.Logging;
    3012import 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;
    3513
    3614import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
    … …  
    4624    @Rule
    4725    @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();
    14127
    14228    /**
    … …  
    16753        assertEquals(expected, Utils.strip(m.group(1)));
    16854    }
    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, wait
    184                     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_EXPIRES
    193      * @throws IOException
    194      */
    195     @Test
    196     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 tile
    209         // only one request to tile server should be made, second should come from cache
    210         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 parameter
    216      * @throws IOException
    217      */
    218     @Test
    219     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_EXPIRES
    226      * @throws IOException
    227      */
    228 
    229     @Test
    230     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 tile
    246         // only one request to tile server should be made, second should come from cache
    247         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_EXPIRES
    253      * @throws IOException
    254      */
    255     @Test
    256     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 tile
    270         // only one request to tile server should be made, second should come from cache
    271         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     @Test
    293     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 margin
    305         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 tile
    309         // only one request to tile server should be made, second should come from cache
    310         tileServer.verify(1, WireMock.getRequestedFor(WireMock.urlEqualTo("/test")));
    311         assertArrayEquals("mock entry".getBytes(StandardCharsets.UTF_8), job.get().getContent());
    312     }
    313 
    31455}
  • /trunk/test/unit/org/openstreetmap/josm/data/imagery/WMTSTileSourceTest.java

    r13733 r13732  
    88import java.io.IOException;
    99import java.net.MalformedURLException;
    10 import java.nio.file.Files;
    11 import java.nio.file.Paths;
    1210import java.util.ArrayList;
    13 import java.util.Arrays;
    14 import java.util.List;
    15 
    16 import org.junit.ClassRule;
     11import java.util.Collection;
     12
    1713import org.junit.Ignore;
     14import org.junit.Rule;
    1815import org.junit.Test;
    1916import org.openstreetmap.gui.jmapviewer.tilesources.TemplatedTMSTileSource;
    … …  
    2219import org.openstreetmap.josm.data.Bounds;
    2320import org.openstreetmap.josm.data.coor.LatLon;
    24 import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType;
    25 import org.openstreetmap.josm.data.imagery.WMTSTileSource.WMTSGetCapabilitiesException;
    2621import org.openstreetmap.josm.data.projection.Projections;
    27 import org.openstreetmap.josm.spi.preferences.Config;
    2822import org.openstreetmap.josm.testutils.JOSMTestRules;
    29 
    30 import com.github.tomakehurst.wiremock.WireMockServer;
    31 import com.github.tomakehurst.wiremock.client.WireMock;
    3223
    3324import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
    … …  
    3728 */
    3829public class WMTSTileSourceTest {
    39 
    40     /**
    41      * Setup test.
    42      */
    43     @ClassRule
    44     @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
    45     public static JOSMTestRules test = new JOSMTestRules().preferences().platform();
    4630
    4731    private ImageryInfo testImageryTMS = new ImageryInfo("test imagery", "http://localhost", "tms", null, null);
    … …  
    6044            "wmts/bug13975-multiple-tile-matrices-for-one-layer-projection.xml");
    6145
     46    /**
     47     * Setup test.
     48     */
     49    @Rule
     50    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
     51    public JOSMTestRules test = new JOSMTestRules();
    6252
    6353    private static ImageryInfo getImagery(String path) {
    6454        try {
    65             ImageryInfo ret = new ImageryInfo(
     55            return new ImageryInfo(
    6656                    "test",
    6757                    new File(path).toURI().toURL().toString()
    6858                    );
    69             ret.setImageryType(ImageryType.WMTS);
    70             return ret;
    7159        } catch (MalformedURLException e) {
    7260            e.printStackTrace();
    … …  
    7664
    7765    @Test
    78     public void testPseudoMercator() throws IOException, WMTSGetCapabilitiesException {
     66    public void testPseudoMercator() throws IOException {
    7967        Main.setProjection(Projections.getProjectionByCode("EPSG:3857"));
    8068        WMTSTileSource testSource = new WMTSTileSource(testImageryPSEUDO_MERCATOR);
    … …  
    10694
    10795    @Test
    108     public void testWALLONIE() throws IOException, WMTSGetCapabilitiesException {
     96    public void testWALLONIE() throws IOException {
    10997        Main.setProjection(Projections.getProjectionByCode("EPSG:31370"));
    11098        WMTSTileSource testSource = new WMTSTileSource(testImageryWALLONIE);
    … …  
    126114    @Test
    127115    @Ignore("disable this test, needs further working") // XXX
    128     public void testWALLONIENoMatrixDimension() throws IOException, WMTSGetCapabilitiesException {
     116    public void testWALLONIENoMatrixDimension() throws IOException {
    129117        Main.setProjection(Projections.getProjectionByCode("EPSG:31370"));
    130118        WMTSTileSource testSource = new WMTSTileSource(getImagery("test/data/wmts/WMTSCapabilities-Wallonie-nomatrixdimension.xml"));
    … …  
    150138
    151139    @Test
    152     public void testWIEN() throws IOException, WMTSGetCapabilitiesException {
     140    public void testWIEN() throws IOException {
    153141        Main.setProjection(Projections.getProjectionByCode("EPSG:3857"));
    154142        WMTSTileSource testSource = new WMTSTileSource(testImageryWIEN);
    … …  
    192180
    193181    @Test
    194     public void testGeoportalTOPOPL() throws IOException, WMTSGetCapabilitiesException {
     182    public void testGeoportalTOPOPL() throws IOException {
    195183        Main.setProjection(Projections.getProjectionByCode("EPSG:4326"));
    196184        WMTSTileSource testSource = new WMTSTileSource(testImageryTOPO_PL);
    … …  
    214202
    215203    @Test
    216     public void testGeoportalORTOPL4326() throws IOException, WMTSGetCapabilitiesException {
     204    public void testGeoportalORTOPL4326() throws IOException {
    217205        Main.setProjection(Projections.getProjectionByCode("EPSG:4326"));
    218206        WMTSTileSource testSource = new WMTSTileSource(testImageryORTO_PL);
    … …  
    223211
    224212    @Test
    225     public void testGeoportalORTOPL2180() throws IOException, WMTSGetCapabilitiesException {
     213    public void testGeoportalORTOPL2180() throws IOException {
    226214        Main.setProjection(Projections.getProjectionByCode("EPSG:2180"));
    227215        WMTSTileSource testSource = new WMTSTileSource(testImageryORTO_PL);
    … …  
    233221
    234222    @Test
    235     public void testTicket12168() throws IOException, WMTSGetCapabilitiesException {
     223    public void testTicket12168() throws IOException {
    236224        Main.setProjection(Projections.getProjectionByCode("EPSG:3857"));
    237225        WMTSTileSource testSource = new WMTSTileSource(testImagery12168);
    … …  
    243231
    244232    @Test
     233    @Ignore("disabled as this needs user action") // XXX
    245234    public void testTwoTileSetsForOneProjection() throws Exception {
    246235        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
    277244    public void testManyLayersScrollbars() throws Exception {
    278245        Main.setProjection(Projections.getProjectionByCode("EPSG:3857"));
    … …  
    304271        Main.setProjection(Projections.getProjectionByCode("EPSG:3857"));
    305272        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"));
    308275        copy.setDefaultLayers(defaultLayers);
    309276        WMTSTileSource testSource = new WMTSTileSource(copy);
    … …  
    319286     * Test WMTS dimension.
    320287     * @throws IOException if any I/O error occurs
    321      * @throws WMTSGetCapabilitiesException
    322288     */
    323289    @Test
    324     public void testDimension() throws IOException, WMTSGetCapabilitiesException {
     290    public void testDimension() throws IOException {
    325291        Main.setProjection(Projections.getProjectionByCode("EPSG:21781"));
    326292        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"));
    329295        info.setDefaultLayers(defaultLayers);
    330296        WMTSTileSource testSource = new WMTSTileSource(info);
    … …  
    334300                testSource.getTileUrl(1, 2, 3)
    335301                );
    336     }
    337 
    338     @Test
    339     public void testDefaultLayer() throws Exception {
    340         // https://gibs.earthdata.nasa.gov/wmts/epsg3857/best/1.0.0/WMTSCapabilities.xml
    341         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 
    377302    }
    378303
  • /trunk/test/unit/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayerTest.java

    r13733 r13732  
    120120            return new TileLoaderFactory() {
    121121                @Override
    122                 public TileLoader makeTileLoader(TileLoaderListener listener, Map<String, String> headers,
    123                         long minimumExpiryTime) {
     122                public TileLoader makeTileLoader(TileLoaderListener listener, Map<String, String> headers) {
    124123                    return null;
    125124                }
  • /trunk/test/unit/org/openstreetmap/josm/gui/layer/WMTSLayerTest.java

    r13733 r13732  
    2222    @Rule
    2323    @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);
    2525
    2626    /**
  • /trunk/test/unit/org/openstreetmap/josm/io/imagery/WMSImageryTest.java

    r13733 r13732  
    66
    77import 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;
     8import java.io.InputStream;
    129
    1310import org.junit.Rule;
    … …  
    1613import org.openstreetmap.josm.io.imagery.WMSImagery.WMSGetCapabilitiesException;
    1714import org.openstreetmap.josm.testutils.JOSMTestRules;
    18 
    19 import com.github.tomakehurst.wiremock.WireMockServer;
    20 import com.github.tomakehurst.wiremock.client.WireMock;
    2115
    2216import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
    … …  
    3226    @Rule
    3327    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
    34     public JOSMTestRules test = new JOSMTestRules().platform().projection();
     28    public JOSMTestRules test = new JOSMTestRules();
    3529
    3630    /**
    … …  
    5549    @Test
    5650    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        }
    7657    }
    7758
    … …  
    8364    @Test
    8465    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        }
    9771    }
    9872}
    99 
Note: See TracChangeset for help on using the changeset viewer.