/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2009-2010, Geomatys
*
* This library 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;
* version 2.1 of the License.
*
* This library 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
* Lesser General Public License for more details.
*/
package org.geotoolkit.wms;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.util.logging.Logging;
import org.geotoolkit.client.AbstractCoverageClient;
import org.geotoolkit.client.CapabilitiesException;
import org.geotoolkit.storage.coverage.CoverageReference;
import org.geotoolkit.storage.coverage.CoverageType;
import org.geotoolkit.util.NamesExt;
import org.geotoolkit.parameter.Parameters;
import org.geotoolkit.security.ClientSecurity;
import org.geotoolkit.storage.DataNode;
import org.geotoolkit.storage.DefaultDataNode;
import org.geotoolkit.wms.auto.GetCapabilitiesAuto;
import org.geotoolkit.wms.v111.GetCapabilities111;
import org.geotoolkit.wms.v111.GetFeatureInfo111;
import org.geotoolkit.wms.v111.GetLegend111;
import org.geotoolkit.wms.v111.GetMap111;
import org.geotoolkit.wms.v130.GetCapabilities130;
import org.geotoolkit.wms.v130.GetFeatureInfo130;
import org.geotoolkit.wms.v130.GetLegend130;
import org.geotoolkit.wms.v130.GetMap130;
import org.geotoolkit.wms.xml.AbstractLayer;
import org.geotoolkit.wms.xml.AbstractWMSCapabilities;
import org.geotoolkit.wms.xml.WMSBindingUtilities;
import org.geotoolkit.wms.xml.WMSVersion;
import org.opengis.util.GenericName;
import org.opengis.parameter.ParameterValueGroup;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.geotoolkit.client.Client;
import org.geotoolkit.storage.DataStoreFactory;
import org.geotoolkit.storage.DataStores;
import org.geotoolkit.storage.coverage.CoverageStore;
/**
* Generates WMS requests objects on a WMS server.
*
* @author Olivier Terral (Geomatys)
* @author Johann Sorel (Geomatys)
* @author Cédric Briançon (Geomatys)
* @module
*/
public class WebMapClient extends AbstractCoverageClient implements Client {
private static final Logger LOGGER = Logging.getLogger("org.geotoolkit.wms");
/**
* Defines the timeout in milliseconds for the GetCapabilities request.
*/
private static final long TIMEOUT_GETCAPS = 20000L;
private AbstractWMSCapabilities capabilities;
private DataNode rootNode = null;
/**
* The request header map for this server
* that contains a set of key-value for HTTP header fields (user-agent, referer, accept-language...)
*/
private final Map<String,String> requestHeaderMap = new HashMap<>();
/**
* Constructor with only URL
* @param serverURL
*/
public WebMapClient(final URL serverURL){
this(serverURL, null, null, null);
}
/**
* Builds a web map server with the given server url and version.
*
* @param serverURL The server base url.
* @param version A string representation of the service version.
* @throws IllegalArgumentException if the version specified is not applyable.
*/
public WebMapClient(final URL serverURL, final String version) {
this(serverURL, WMSVersion.getVersion(version));
}
/**
* Builds a web map server with the given server url and version.
*
* @param serverURL The server base url.
* @param version The service version.
*/
public WebMapClient(final URL serverURL, final WMSVersion version) {
this(serverURL, version, null);
}
/**
* Builds a web map server with the given server url and version.
*
* @param serverURL The server base url.
* @param security The server security.
* @param version The service version.
*/
public WebMapClient(final URL serverURL, final ClientSecurity security,final WMSVersion version) {
this(serverURL, security, version, null);
}
/**
* Builds a web map server with the given server url, version and getCapabilities response.
*
* @param serverURL The server base url.
* @param version A string representation of the service version.
* @param capabilities A getCapabilities response.
* @throws IllegalArgumentException if the version specified is not applyable.
*/
public WebMapClient(final URL serverURL, final String version, final AbstractWMSCapabilities capabilities) {
this(serverURL, WMSVersion.getVersion(version), capabilities);
}
/**
* Builds a web map server with the given server url, version and getCapabilities response.
*
* @param serverURL The server base url.
* @param version A string representation of the service version.
* @param capabilities A getCapabilities response.
*/
public WebMapClient(final URL serverURL, final WMSVersion version,
final AbstractWMSCapabilities capabilities) {
this(serverURL,null,version,capabilities);
}
/**
* Builds a web map server with the given server url, version and getCapabilities response.
*
* @param serverURL The server base url.
* @param security The server security.
* @param version A string representation of the service version.
* @param capabilities A getCapabilities response.
*/
public WebMapClient(final URL serverURL, final ClientSecurity security,
WMSVersion version, final AbstractWMSCapabilities capabilities) {
super(create(WMSClientFactory.PARAMETERS, serverURL, security));
this.capabilities = capabilities;
//if version is null, call getCapabilities to found service version
if(version==null){
if(LOGGER.isLoggable(Level.FINE)){
LOGGER.log(Level.FINE, "No version define : search it on getCapabilities");
}
try {
if(capabilities!=null){
this.capabilities = capabilities;
}else{
this.capabilities = getCapabilities();
}
//set version
version = WMSVersion.getVersion(this.capabilities.getVersion());
} catch (CapabilitiesException e) {
LOGGER.log(Level.WARNING, e.getLocalizedMessage(), e);
version = WMSVersion.v130;
}
}
Parameters.getOrCreate(WMSClientFactory.VERSION, parameters).setValue(version.getCode());
}
public WebMapClient(ParameterValueGroup params) {
super(params);
}
@Override
public WMSClientFactory getFactory() {
return (WMSClientFactory)DataStores.getFactoryById(WMSClientFactory.NAME);
}
/**
* {@inheritDoc}
*/
@Override
public URI getURI(){
try {
return serverURL.toURI();
} catch (URISyntaxException ex) {
LOGGER.log(Level.WARNING, ex.getLocalizedMessage(), ex);
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public URL getURL() {
return serverURL;
}
/**
* Returns the {@linkplain AbstractWMSCapabilities capabilities} response for this
* request if the server answers in less than 20s, otherwise throws a
* {@link CapabilitiesException}.
*
* @return {@linkplain AbstractWMSCapabilities capabilities} response but never {@code null}.
* @throws CapabilitiesException
* @see {@link #getCapabilities(long)}
*/
public AbstractWMSCapabilities getCapabilities() throws CapabilitiesException{
return getCapabilities(TIMEOUT_GETCAPS);
}
/**
* Returns the {@linkplain AbstractWMSCapabilities capabilities} response for this
* request if the server answers before the specified timeout, otherwise throws a
* {@link CapabilitiesException}.
*
* @param timeout Timeout in milliseconds
* @return {@linkplain AbstractWMSCapabilities capabilities} response but never {@code null}.
* @throws CapabilitiesException
*/
public AbstractWMSCapabilities getCapabilities(final long timeout) throws CapabilitiesException{
if (capabilities != null) {
return capabilities;
}
final CapabilitiesException[] exception = new CapabilitiesException[1];
//Thread to prevent infinite request on a server
final Thread thread = new Thread() {
@Override
public void run() {
final GetCapabilitiesRequest getCaps = createGetCapabilities();
//Filling the request header map from the map of the layer's server
final Map<String, String> headerMap = getRequestHeaderMap();
getCaps.getHeaderMap().putAll(headerMap);
try {
System.out.println(getCaps.getURL());
capabilities = WMSBindingUtilities.unmarshall(getCaps.getResponseStream(), getVersion());
} catch (Exception ex) {
capabilities = null;
try {
exception[0] = new CapabilitiesException("Wrong URL, the server doesn't answer : " +
createGetCapabilities().getURL().toString(), ex);
} catch (MalformedURLException ex1) {
exception[0] = new CapabilitiesException("Malformed URL, the server doesn't answer. ", ex);
}
}
}
};
thread.start();
final long start = System.currentTimeMillis();
try {
thread.join(timeout);
} catch (InterruptedException ex) {
throw new CapabilitiesException("The thread to obtain GetCapabilities doesn't answer.");
}
if(exception[0] != null){
throw exception[0];
}
if ((System.currentTimeMillis() - start) > timeout) {
throw new CapabilitiesException("TimeOut error, the server takes too much time to answer.");
}
//force throw CapabilitiesException if the returned capabilities object is null
if(capabilities == null){
throw new CapabilitiesException("The capabilities document is null.");
}
WMSVersion version = WMSVersion.getVersion(this.capabilities.getVersion());
Parameters.getOrCreate(WMSClientFactory.VERSION, parameters).setValue(version.getCode());
return capabilities;
}
/**
* Returns the request version.
* @return
*/
public WMSVersion getVersion() {
return WMSVersion.getVersion(Parameters.value(WMSClientFactory.VERSION, parameters));
}
/**
* Returns the request object, in the version chosen.
*
* @return
* @throws IllegalArgumentException if the version requested is not supported.
*/
public GetMapRequest createGetMap() {
switch (getVersion()) {
case v111:
return new GetMap111(this,getClientSecurity());
case v130:
return new GetMap130(this,getClientSecurity());
default:
throw new IllegalArgumentException("Version was not defined");
}
}
/**
* Returns the request object, in the version chosen.
*
* @return
* @throws IllegalArgumentException if the version requested is not supported.
*/
public GetCapabilitiesRequest createGetCapabilities() {
switch (getVersion()) {
case v111:
return new GetCapabilities111(serverURL.toString(),getClientSecurity());
case v130:
return new GetCapabilities130(serverURL.toString(),getClientSecurity());
default:
if(LOGGER.isLoggable(Level.FINE)){
LOGGER.log(Level.FINE, "Version was not defined");
}
return new GetCapabilitiesAuto(serverURL.toString(), getClientSecurity());
}
}
/**
* Returns the request object, in the version chosen.
*
* @return
* @throws IllegalArgumentException if the version requested is not supported.
*/
public GetLegendRequest createGetLegend(){
switch (getVersion()) {
case v111:
return new GetLegend111(serverURL.toString(),getClientSecurity());
case v130:
return new GetLegend130(serverURL.toString(),getClientSecurity());
default:
throw new IllegalArgumentException("Version was not defined");
}
}
/**
* Returns the request object, in the version chosen.
*
* @return
* @throws IllegalArgumentException if the version requested is not supported.
*/
public GetFeatureInfoRequest createGetFeatureInfo() {
switch (getVersion()) {
case v111:
return new GetFeatureInfo111(this,getClientSecurity());
case v130:
return new GetFeatureInfo130(this,getClientSecurity());
default:
throw new IllegalArgumentException("Version was not defined");
}
}
/**
* Returns the request header map for this server.
* @return
*/
public Map<String,String> getRequestHeaderMap() {
return requestHeaderMap;
}
@Override
public synchronized DataNode getRootNode() throws DataStoreException {
if(rootNode == null){
rootNode = new DefaultDataNode();
final AbstractWMSCapabilities capa;
try {
capa = getCapabilities();
} catch (CapabilitiesException ex) {
throw new DataStoreException(ex);
}
final List<AbstractLayer> layers = capa.getLayers();
for(AbstractLayer al : layers){
final String name = al.getName();
if(name != null){
final GenericName nn = NamesExt.valueOf(name);
final CoverageReference ref = createReference(nn);
rootNode.getChildren().add(ref);
}
}
}
return rootNode;
}
/**
* Override by WMS-c and NCWMS.
*
* @param name
* @return
*/
protected CoverageReference createReference(GenericName name) throws DataStoreException{
return new WMSCoverageReference(this,name);
}
@Override
public void close() {
}
@Override
public CoverageReference create(GenericName name) throws DataStoreException {
throw new DataStoreException("Can not create new coverage.");
}
@Override
public void delete(GenericName name) throws DataStoreException {
throw new DataStoreException("Can not create new coverage.");
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("WebMapServer[");
sb.append("serverUrl: ").append(serverURL).append(", ")
.append("version: ").append(getVersion()).append("]");
return sb.toString();
}
@Override
public CoverageType getType() {
return CoverageType.GRID;
}
}