Java tutorial
/** * This program is free software: you can redistribute it and/or modify it under the terms of the * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * <p>This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * <p>You should have received a copy of the GNU Lesser General Public License along with this * program. If not, see <http://www.gnu.org/licenses/>. * * @author Arne Kepp, The Open Planning Project, Copyright 2008 */ package org.geowebcache.config.wms; import com.google.common.collect.Sets; import java.io.IOException; import java.net.URL; import java.util.*; import java.util.function.UnaryOperator; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import javax.annotation.Nullable; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.geotools.data.ows.SimpleHttpClient; import org.geotools.ows.ServiceException; import org.geotools.ows.wms.*; import org.geotools.ows.wms.xml.Dimension; import org.geotools.ows.wms.xml.Extent; import org.geotools.util.PreventLocalEntityResolver; import org.geotools.xml.XMLHandlerHints; import org.geowebcache.GeoWebCacheException; import org.geowebcache.config.ConfigurationException; import org.geowebcache.config.DefaultingConfiguration; import org.geowebcache.config.GridSetConfiguration; import org.geowebcache.config.TileLayerConfiguration; import org.geowebcache.config.legends.LegendRawInfo; import org.geowebcache.config.legends.LegendsRawInfo; import org.geowebcache.config.wms.parameters.NaiveWMSDimensionFilter; import org.geowebcache.filter.parameters.ParameterFilter; import org.geowebcache.filter.parameters.RegexParameterFilter; import org.geowebcache.filter.parameters.StringParameterFilter; import org.geowebcache.grid.*; import org.geowebcache.layer.TileLayer; import org.geowebcache.layer.meta.LayerMetaInformation; import org.geowebcache.layer.meta.MetadataURL; import org.geowebcache.layer.wms.WMSHttpHelper; import org.geowebcache.layer.wms.WMSLayer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; public class GetCapabilitiesConfiguration implements TileLayerConfiguration, GridSetConfiguration { private static Log log = LogFactory.getLog(org.geowebcache.config.wms.GetCapabilitiesConfiguration.class); // regex patterns used to parse legends urls parameters private static final Pattern LEGEND_WIDTH_PATTERN = Pattern.compile(".*width=(\\d+).*", Pattern.CASE_INSENSITIVE); private static final Pattern LEGEND_HEIGHT_PATTERN = Pattern.compile(".*height=(\\d+).*", Pattern.CASE_INSENSITIVE); private static final Pattern LEGEND_FORMAT_PATTERN = Pattern.compile(".*format=([^&]+).*", Pattern.CASE_INSENSITIVE); private GridSetBroker gridSetBroker; private String url = null; private int backendTimeout = 120; private String mimeTypes = null; private String metaTiling = null; private String vendorParameters = null; private Map<String, String> cachedParameters = null; private boolean allowCacheBypass = false; private final HashMap<String, TileLayer> layers; private DefaultingConfiguration primaryConfig; private Map<String, GridSet> generatedGridSets = new HashMap<>(); public GetCapabilitiesConfiguration(GridSetBroker gridSetBroker, String url, String mimeTypes, String metaTiling, String allowCacheBypass) { this.gridSetBroker = gridSetBroker; this.url = url; this.mimeTypes = mimeTypes; this.metaTiling = metaTiling; this.layers = new HashMap<String, TileLayer>(); if (Boolean.parseBoolean(allowCacheBypass)) { this.allowCacheBypass = true; } log.info("Constructing from url " + url); } public GetCapabilitiesConfiguration(GridSetBroker gridSetBroker, String url, String mimeTypes, String metaTiling, String vendorParameters, String allowCacheBypass) { this.gridSetBroker = gridSetBroker; this.url = url; this.mimeTypes = mimeTypes; this.metaTiling = metaTiling; this.vendorParameters = vendorParameters; this.layers = new HashMap<String, TileLayer>(); if (Boolean.parseBoolean(allowCacheBypass)) { this.allowCacheBypass = true; } log.info("Constructing from url " + url); } public GetCapabilitiesConfiguration(GridSetBroker gridSetBroker, String url, String mimeTypes, String metaTiling, String vendorParameters, Map<String, String> cachedParameters, String allowCacheBypass) { this(gridSetBroker, url, mimeTypes, metaTiling, vendorParameters, allowCacheBypass); this.cachedParameters = cachedParameters; } /** * Optionally used by Spring * * @param backendTimeout */ public void setBackendTimeout(int backendTimeout) { this.backendTimeout = backendTimeout; } /** * Identifier for this TileLayerConfiguration instance * * @return the URL given to the constructor */ public String getIdentifier() { return url; } /** * Gets the XML document and parses it, creates WMSLayers for the relevant * * @return the layers described at the given URL */ private synchronized List<TileLayer> getTileLayers(boolean reload) throws GeoWebCacheException { final List<TileLayer> layers; final WebMapServer wms; try { wms = getWMS(); } catch (ServiceException | IOException e) { throw new ConfigurationException( "Could not retrieve (or parse) GetCapaibilities " + this.url + " :" + e.getMessage(), e); } String wmsUrl = getWMSUrl(wms); log.info("Using GetCapabilities " + wmsUrl + " to generate URLs for WMS requests"); String urlVersion = parseVersion(url); layers = getLayers(wms, wmsUrl, urlVersion); if (layers == null || layers.size() < 1) { log.error("Unable to find any layers based on " + url); } else { log.info("Loaded " + layers.size() + " layers from " + url); } return layers; } /** * Finds URL to WMS service and attempts to slice away the service parameter, since we will add * that anyway. * * @param wms * @return */ private String getWMSUrl(WebMapServer wms) { // // http://sigma.openplans.org:8080/geoserver/wms?SERVICE=WMS& String wmsUrl = wms.getCapabilities().getRequest().getGetCapabilities().getGet().toString(); int queryStart = wmsUrl.lastIndexOf("?"); if (queryStart > 0) { String preQuery = wmsUrl.substring(queryStart); if (preQuery.equalsIgnoreCase("?service=wms&")) { wmsUrl = wmsUrl.substring(0, wmsUrl.lastIndexOf("?")); } } return wmsUrl; } private List<TileLayer> getLayers(WebMapServer wms, String wmsUrl, String urlVersion) throws GeoWebCacheException { List<TileLayer> layers = new LinkedList<TileLayer>(); WMSCapabilities capabilities = wms.getCapabilities(); if (capabilities == null) { throw new ConfigurationException("Unable to get capabitilies from " + wmsUrl); } WMSHttpHelper sourceHelper = new WMSHttpHelper(); List<Layer> layerList = capabilities.getLayerList(); Iterator<Layer> layerIter = layerList.iterator(); while (layerIter.hasNext()) { Layer layer = layerIter.next(); String name = layer.getName(); String stylesStr = ""; String title = layer.getTitle(); String description = layer.get_abstract(); LayerMetaInformation layerMetaInfo = null; if (title != null || description != null) { layerMetaInfo = new LayerMetaInformation(title, description, null, null); } boolean queryable = layer.isQueryable(); if (name != null) { LinkedList<ParameterFilter> paramFilters = new LinkedList<ParameterFilter>(); List<StyleImpl> styles = layer.getStyles(); StringBuffer buf = new StringBuffer(); if (styles != null) { Iterator<StyleImpl> iter = styles.iterator(); boolean hasOne = false; while (iter.hasNext()) { if (hasOne) { buf.append(","); } buf.append(iter.next().getName()); hasOne = true; } stylesStr = buf.toString(); // set styles parameters StringParameterFilter stylesParameterFilter = new StringParameterFilter(); stylesParameterFilter.setKey("STYLES"); stylesParameterFilter .setValues(styles.stream().map(StyleImpl::getName).collect(Collectors.toList())); paramFilters.add(stylesParameterFilter); } double minX = layer.getLatLonBoundingBox().getMinX(); double minY = layer.getLatLonBoundingBox().getMinY(); double maxX = layer.getLatLonBoundingBox().getMaxX(); double maxY = layer.getLatLonBoundingBox().getMaxY(); BoundingBox bounds4326 = new BoundingBox(minX, minY, maxX, maxY); log.info("Found layer: " + layer.getName() + " with LatLon bbox " + bounds4326.toString()); BoundingBox bounds3785 = new BoundingBox(longToSphericalMercatorX(minX), latToSphericalMercatorY(minY), longToSphericalMercatorX(maxX), latToSphericalMercatorY(maxY)); String[] wmsUrls = { wmsUrl }; for (Dimension dimension : layer.getDimensions().values()) { Extent dimExtent = layer.getExtent(dimension.getName()); paramFilters.add(new NaiveWMSDimensionFilter(dimension, dimExtent)); } if (this.cachedParameters != null) { for (Map.Entry<String, String> entry : this.cachedParameters.entrySet()) { if (!"".equals(entry.getKey())) { RegexParameterFilter f = new RegexParameterFilter(); f.setRegex(".*"); f.setKey(entry.getKey()); f.setDefaultValue(entry.getValue()); paramFilters.add(f); } } } WMSLayer wmsLayer = null; try { wmsLayer = getLayer(name, wmsUrls, bounds4326, bounds3785, stylesStr, queryable, layer.getBoundingBoxes(), paramFilters); } catch (GeoWebCacheException gwc) { log.error("Error creating " + layer.getName() + ": " + gwc.getMessage()); } if (wmsLayer != null) { // Finalize with some defaults wmsLayer.setCacheBypassAllowed(allowCacheBypass); wmsLayer.setBackendTimeout(backendTimeout); wmsLayer.setMetaInformation(layerMetaInfo); if (urlVersion != null) { wmsLayer.setVersion(urlVersion); } else { String wmsVersion = capabilities.getVersion(); if (wmsVersion != null && wmsVersion.length() > 0) { wmsLayer.setVersion(wmsVersion); } } wmsLayer.setSourceHelper(sourceHelper); List<org.geotools.ows.wms.xml.MetadataURL> metadataURLs = layer.getMetadataURL(); if (metadataURLs != null && !metadataURLs.isEmpty()) { List<MetadataURL> convertedMetadataURLs = new ArrayList<MetadataURL>(); for (org.geotools.ows.wms.xml.MetadataURL metadataURL : metadataURLs) { convertedMetadataURLs.add(new MetadataURL(metadataURL.getType(), metadataURL.getFormat(), metadataURL.getUrl())); } wmsLayer.setMetadataURLs(convertedMetadataURLs); } // add styles legend information wmsLayer.setLegends(extractLegendsInfo(styles)); layers.add(wmsLayer); } } } return layers; } /** Helper method that extracts from a legend url the width, height and format parameters. */ private LegendsRawInfo extractLegendsInfo(List<StyleImpl> styles) { LegendsRawInfo legendsRawInfo = new LegendsRawInfo(); // setting some acceptable default values legendsRawInfo.setDefaultWidth(20); legendsRawInfo.setDefaultHeight(20); legendsRawInfo.setDefaultFormat("image/png"); for (StyleImpl style : styles) { // extracting legend information from each style LegendRawInfo legendRawInfo = new LegendRawInfo(); legendRawInfo.setStyle(style.getName()); List legendUrls = style.getLegendURLs(); if (legendUrls != null && !legendUrls.isEmpty()) { String legendUrl = (String) legendUrls.get(0); // let's see if we can extract width, height and format from the style legend url legendRawInfo.setWidth(extractIntegerParameter(legendUrl, LEGEND_WIDTH_PATTERN)); legendRawInfo.setHeight(extractIntegerParameter(legendUrl, LEGEND_HEIGHT_PATTERN)); legendRawInfo.setFormat(extractParameter(legendUrl, LEGEND_FORMAT_PATTERN)); // setting the complete legend url legendRawInfo.setCompleteUrl(legendUrl); } legendsRawInfo.addLegendRawInfo(legendRawInfo); } return legendsRawInfo; } /** Helper method that simply extracts from the provided url a certain parameter. */ private String extractParameter(String url, Pattern pattern) { Matcher matcher = pattern.matcher(url); if (matcher.matches()) { return matcher.group(1); } return null; } /** Helper method that simply extracts from the provided url a certain integer parameter. */ private Integer extractIntegerParameter(String url, Pattern pattern) { String value = extractParameter(url, pattern); if (value == null) { return null; } return Integer.valueOf(value); } private WMSLayer getLayer(String name, String[] wmsurl, BoundingBox bounds4326, BoundingBox bounds3785, String stylesStr, boolean queryable, Map<String, CRSEnvelope> additionalBounds, List<ParameterFilter> paramFilters) throws GeoWebCacheException { Hashtable<String, GridSubset> grids = new Hashtable<String, GridSubset>(2); grids.put(gridSetBroker.getWorldEpsg4326().getName(), GridSubsetFactory.createGridSubSet(gridSetBroker.getWorldEpsg4326(), bounds4326, 0, 30)); grids.put(gridSetBroker.getWorldEpsg3857().getName(), GridSubsetFactory.createGridSubSet(gridSetBroker.getWorldEpsg3857(), bounds3785, 0, 30)); if (additionalBounds != null && additionalBounds.size() > 0) { Iterator<CRSEnvelope> iter = additionalBounds.values().iterator(); while (iter.hasNext()) { CRSEnvelope env = iter.next(); SRS srs = null; if (env.getEPSGCode() != null) { srs = SRS.getSRS(env.getEPSGCode()); } if (srs == null) { log.error(env.toString() + " has no EPSG code"); } else if (srs.getNumber() == 4326 || srs.getNumber() == 900913 || srs.getNumber() == 3857) { log.debug("Skipping " + srs.toString() + " for " + name); } else { String gridSetName = name + ":" + srs.toString(); BoundingBox extent = new BoundingBox(env.getMinX(), env.getMinY(), env.getMaxX(), env.getMaxY()); GridSet gridSet = GridSetFactory.createGridSet(gridSetName, srs, extent, false, 25, null, GridSetFactory.DEFAULT_PIXEL_SIZE_METER, 256, 256, false); grids.put(gridSetName, GridSubsetFactory.createGridSubSet(gridSet)); } } } List<String> mimeFormats = null; if (this.mimeTypes != null) { String[] mimeFormatArray = this.mimeTypes.split(","); mimeFormats = new ArrayList<String>(mimeFormatArray.length); // This is stupid... but oh well, we're only doing it once for (int i = 0; i < mimeFormatArray.length; i++) { mimeFormats.add(mimeFormatArray[i]); } } else { mimeFormats = new ArrayList<String>(3); mimeFormats.add("image/png"); mimeFormats.add("image/png8"); mimeFormats.add("image/jpeg"); } String[] metaStrings = this.metaTiling.split("x"); int[] metaWidthHeight = { Integer.parseInt(metaStrings[0]), Integer.parseInt(metaStrings[1]) }; return new WMSLayer(name, wmsurl, stylesStr, name, mimeFormats, grids, paramFilters, metaWidthHeight, this.vendorParameters, queryable, null); } WebMapServer getWMS() throws IOException, ServiceException { Map<String, Object> hints = new HashMap<>(); hints.put(XMLHandlerHints.ENTITY_RESOLVER, PreventLocalEntityResolver.INSTANCE); return new WebMapServer(new URL(url), new SimpleHttpClient(), hints); } private String parseVersion(String url) { String tmp = url.toLowerCase(); int start = tmp.indexOf("version="); if (start == -1) { return null; } start += "version=".length(); int stop = tmp.indexOf("&", start); if (stop > 0) { return tmp.substring(start, stop); } else { return tmp.substring(start); } } private double longToSphericalMercatorX(double x) { return (x / 180.0) * 20037508.34; } private double latToSphericalMercatorY(double y) { if (y > 85.05112) { y = 85.05112; } if (y < -85.05112) { y = -85.05112; } y = (Math.PI / 180.0) * y; double tmp = Math.PI / 4.0 + y / 2.0; return 20037508.34 * Math.log(Math.tan(tmp)) / Math.PI; } public void afterPropertiesSet() throws GeoWebCacheException { List<TileLayer> tileLayers = getTileLayers(true); Set<String> brokerNames = gridSetBroker.getGridSetNames(); for (TileLayer layer : tileLayers) { layer.initialize(gridSetBroker); if (primaryConfig != null) { primaryConfig.setDefaultValues(layer); } else if (log.isErrorEnabled()) { log.error("GetCapabilitiesConfiguration could not initialize a layer with default " + "values as it does not have a global configuration to delegate to."); } layers.put(layer.getName(), layer); Map<String, GridSet> generatedForLayer = Sets.difference(layer.getGridSubsets(), brokerNames).stream() .map(layer::getGridSubset).map(GridSubset::getGridSet) .collect(Collectors.toMap(GridSet::getName, UnaryOperator.identity())); generatedGridSets.putAll(generatedForLayer); } } /** * @see TileLayerConfiguration#getTileLayers() * @deprecated */ @Deprecated public List<TileLayer> getTileLayers() { return Collections.unmodifiableList(new ArrayList<TileLayer>(layers.values())); } /** @see TileLayerConfiguration#getLayers() */ public Collection<? extends TileLayer> getLayers() { return Collections.unmodifiableList(new ArrayList<>(layers.values())); } /** @see TileLayerConfiguration#getLayerNames() */ public Set<String> getLayerNames() { return new HashSet<String>(layers.keySet()); } /** @see TileLayerConfiguration#getTileLayerNames() */ @Deprecated public Set<String> getTileLayerNames() { return getLayerNames(); } /** @see TileLayerConfiguration#containsLayer(java.lang.String) */ public boolean containsLayer(String layerName) { return getLayer(layerName) != null; } /** @see TileLayerConfiguration#getTileLayer(java.lang.String) */ public Optional<TileLayer> getLayer(String layerName) { return Optional.ofNullable(layers.get(layerName)); } /** @see TileLayerConfiguration#getTileLayerById(java.lang.String) */ @Deprecated public @Nullable TileLayer getTileLayerById(String layerId) { // this configuration does not differentiate between layer identifier and identity return getLayer(layerId).orElse(null); } /** @see TileLayerConfiguration#getTileLayer(java.lang.String) */ @Deprecated public @Nullable TileLayer getTileLayer(String layerName) { return getLayer(layerName).orElse(null); } /** @see TileLayerConfiguration#getLayerCount() */ public int getLayerCount() { return layers.size(); } /** @see TileLayerConfiguration#getTileLayerCount() */ @Deprecated public int getTileLayerCount() { return getLayerCount(); } /** @see TileLayerConfiguration#removeLayer(java.lang.String) */ // TODO: Why doesn't this throw an IllegalArgument exception: read-only? public void removeLayer(String layerName) throws NoSuchElementException { if (layers.remove(layerName) == null) { throw new NoSuchElementException("Layer " + layerName + " does not exist"); } } /** @see TileLayerConfiguration#modifyLayer(org.geowebcache.layer.TileLayer) */ public void modifyLayer(TileLayer tl) throws NoSuchElementException { throw new UnsupportedOperationException("modifyLayer is not supported by " + getClass().getSimpleName()); } /** @see TileLayerConfiguration#renameLayer(String, String) */ public void renameLayer(String oldName, String newName) throws NoSuchElementException, IllegalArgumentException { throw new UnsupportedOperationException("renameLayer is not supported by " + getClass().getSimpleName()); } /** * @return {@code false} * @see TileLayerConfiguration#canSave(org.geowebcache.layer.TileLayer) */ public boolean canSave(TileLayer tl) { return false; } /** @see TileLayerConfiguration#addLayer(org.geowebcache.layer.TileLayer) */ public void addLayer(TileLayer tl) throws IllegalArgumentException { if (tl == null) { throw new NullPointerException(); } throw new IllegalArgumentException( "This is a read only configuration object, can't add tile layer " + tl.getName()); } /** Get the global configuration delegated to. */ protected DefaultingConfiguration getPrimaryConfig() { return primaryConfig; } /** Set the global configuration object to delegate to. */ public void setPrimaryConfig(DefaultingConfiguration primaryConfig) { this.primaryConfig = primaryConfig; } @Override public String getLocation() { return this.url; } @Override public void addGridSet(GridSet gridSet) throws IllegalArgumentException { if (gridSet == null) { throw new NullPointerException(); } throw new UnsupportedOperationException( "This is a read only configuration object, can't add gridset " + gridSet.getName()); } @Override public void removeGridSet(String gridSetName) { throw new UnsupportedOperationException( "This is a read only configuration object, can't add gridset " + gridSetName); } @Override public Optional<GridSet> getGridSet(String name) throws NoSuchElementException { return Optional.ofNullable(generatedGridSets.get(name)).map(GridSet::new); } @Override public Collection<GridSet> getGridSets() { return generatedGridSets.values().stream().map(GridSet::new).collect(Collectors.toList()); } @Override public void modifyGridSet(GridSet gridSet) throws NoSuchElementException, IllegalArgumentException, UnsupportedOperationException { throw new UnsupportedOperationException(); } @Override public void renameGridSet(String oldName, String newName) throws NoSuchElementException, IllegalArgumentException, UnsupportedOperationException { throw new UnsupportedOperationException(); } @Override public boolean canSave(GridSet gridset) { return false; } @Autowired @Override public void setGridSetBroker(@Qualifier("gwcGridSetBroker") GridSetBroker broker) { this.gridSetBroker = broker; } @Override public void deinitialize() throws Exception { this.generatedGridSets.clear(); this.layers.clear(); } }