package com.revolsys.record.io.format.esri.rest;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;
import com.revolsys.collection.map.MapEx;
import com.revolsys.collection.map.Maps;
import com.revolsys.io.PathName;
import com.revolsys.io.map.MapObjectFactoryRegistry;
import com.revolsys.logging.Logs;
import com.revolsys.record.io.RecordReader;
import com.revolsys.record.io.format.esri.rest.map.FeatureLayer;
import com.revolsys.record.io.format.esri.rest.map.FeatureService;
import com.revolsys.record.io.format.esri.rest.map.MapService;
import com.revolsys.record.query.Query;
import com.revolsys.spring.resource.UrlResource;
import com.revolsys.util.PasswordUtil;
import com.revolsys.util.Property;
import com.revolsys.util.function.SupplierWithProperties;
import com.revolsys.webservice.WebServiceResource;
public class ArcGisRestCatalog extends ArcGisResponse<CatalogElement> {
public static final String J_TYPE = "arcGisRestServer";
private static final Map<String, Function<ArcGisRestServiceContainer, ArcGisRestService>> SERVICE_FACTORY_BY_TYPE = Maps
.<String, Function<ArcGisRestServiceContainer, ArcGisRestService>> buildHash()//
.add("MapServer", MapService::new) //
.add("FeatureServer", FeatureService::new) //
.getMap();
@SuppressWarnings("unchecked")
public static void mapObjectFactoryInit() {
MapObjectFactoryRegistry.newFactory(J_TYPE, "Arc GIS REST Server",
ArcGisRestCatalog::newArcGisRestCatalog);
MapObjectFactoryRegistry.newFactory("arcGisRestServerLayerRecordReaderFactory",
"Factory to create a RecordReader from a ArcGis Server later", (properties) -> {
final String serverUrl = (String)properties.get("serverUrl");
final String layerPath = (String)properties.get("layerPath");
final Supplier<RecordReader> factory = () -> {
final FeatureLayer layer = FeatureLayer.getRecordLayerDescription(serverUrl, layerPath);
if (layer == null) {
throw new RuntimeException("Cannot find layer: " + layerPath + " on " + serverUrl);
} else {
final RecordReader reader = layer.newRecordReader((Query)null, true);
final Map<String, Object> readerProperties = (Map<String, Object>)properties
.get("readerProperties");
reader.setProperties(readerProperties);
return reader;
}
};
return new SupplierWithProperties<>(factory, properties);
});
}
public static ArcGisRestCatalog newArcGisRestCatalog(
final Map<String, ? extends Object> properties) {
final String serviceUrl = (String)properties.get("serviceUrl");
if (Property.hasValue(serviceUrl)) {
final ArcGisRestCatalog service = newArcGisRestCatalog(serviceUrl);
service.setProperties(properties);
return service;
} else {
throw new IllegalArgumentException("Missing serviceUrl");
}
}
public static ArcGisRestCatalog newArcGisRestCatalog(String rootUrl) {
rootUrl = rootUrl.replaceAll("/+$", "");
if (rootUrl.endsWith("/services") || rootUrl.endsWith("%2fservices")) {
return new ArcGisRestCatalog(rootUrl);
} else if (rootUrl.endsWith("/rest")) {
return new ArcGisRestCatalog(rootUrl + "/services");
} else {
final int index = rootUrl.indexOf("/rest/services");
if (index == -1) {
throw new IllegalArgumentException(
"ArgGIS rest services must include the path /rest/services");
} else {
final String url = rootUrl.substring(0, index + 14);
return new ArcGisRestCatalog(url);
}
}
}
private Map<String, CatalogElement> childByName = new HashMap<>();
private List<CatalogElement> children = Collections.emptyList();
private String username;
private String password;
private ArcGisRestCatalog(final ArcGisRestCatalog parent, final String path) {
super(parent, path);
}
public ArcGisRestCatalog(final String rootUrl) {
setServiceUrl(new UrlResource(rootUrl));
}
@Override
@SuppressWarnings("unchecked")
public <C extends WebServiceResource> C getChild(final String name) {
refreshIfNeeded();
if (name == null) {
return null;
} else {
final String childKey = name.toLowerCase();
final CatalogElement child = this.childByName.get(childKey);
if (child == null) {
final ArcGisRestCatalog folder = new ArcGisRestCatalog(this, name);
folder.refresh();
return (C)folder;
}
return (C)child;
}
}
@Override
public List<CatalogElement> getChildren() {
refreshIfNeeded();
return this.children;
}
@Override
public String getIconName() {
return "folder:world";
}
@Override
public String getName() {
String name = super.getName();
if (name == null) {
final PathName pathName = getPathName();
if (pathName == null || pathName.equals("/")) {
final UrlResource resourceUrl = getServiceUrl();
try {
final URI uri = resourceUrl.getUri();
return uri.getHost();
} catch (final Throwable e) {
return "???";
}
} else {
name = pathName.getName();
setName(name);
}
}
return name;
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getPathElement() {
return super.getName();
}
@SuppressWarnings("unchecked")
public <T extends ArcGisRestService> T getService(final String name,
final Class<T> serviceClass) {
final CatalogElement child = getChild(name);
if (child != null) {
if (serviceClass.isAssignableFrom(child.getClass())) {
return (T)child;
} else {
throw new IllegalArgumentException(
"ArcGIS REST service is not a " + serviceClass.getName() + ": " + name);
}
}
return null;
}
@Override
public String getUsername() {
return this.username;
}
@Override
public String getWebServiceTypeName() {
return J_TYPE;
}
@Override
protected void initialize(final MapEx properties) {
final List<CatalogElement> children = new ArrayList<>();
final Map<String, CatalogElement> childByName = new HashMap<>();
final List<String> folderNames = properties.getValue("folders", Collections.emptyList());
for (final String folderPath : folderNames) {
final String folderName = PathName.newPathName(folderPath).getName();
final ArcGisRestCatalog folder = new ArcGisRestCatalog(this, folderName);
children.add(folder);
final String childKey = folderName.toLowerCase();
childByName.put(childKey, folder);
}
final List<MapEx> serviceDescriptions = properties.getValue("services",
Collections.emptyList());
for (final MapEx serviceDescription : serviceDescriptions) {
final String serviceContainerPath = serviceDescription.getString("name");
final String serviceType = serviceDescription.getString("type");
final String serviceContainerName = PathName.newPathName(serviceContainerPath).getName();
try {
final String childKey = serviceContainerName.toLowerCase();
ArcGisRestServiceContainer container = (ArcGisRestServiceContainer)childByName
.get(childKey);
if (container == null) {
container = new ArcGisRestServiceContainer(this, serviceContainerName);
childByName.put(childKey, container);
children.add(container);
}
ArcGisRestService service;
final Function<ArcGisRestServiceContainer, ArcGisRestService> serviceFactory = SERVICE_FACTORY_BY_TYPE
.get(serviceType);
if (serviceFactory == null) {
service = new ArcGisRestService(container, serviceType);
} else {
service = serviceFactory.apply(container);
}
container.addService(service);
} catch (final Throwable e) {
Logs.error(this, "Unable to get service: " + getServiceUrl() + "/" + serviceContainerPath,
e);
}
}
this.childByName = Collections.unmodifiableMap(childByName);
this.children = Collections.unmodifiableList(children);
}
@Override
protected UrlResource newServiceUrlResource(final Map<String, Object> parameters) {
return getServiceUrl().newUrlResource(parameters);
}
@Override
public void setPassword(final String password) {
this.password = password;
final UrlResource serviceUrl = getServiceUrl();
if (serviceUrl != null) {
setServiceUrl(serviceUrl.newUrlResourceAuthorization(this.username, this.password));
}
}
@Override
public void setServiceUrl(final UrlResource serviceUrl) {
super.setServiceUrl(serviceUrl.newUrlResourceAuthorization(this.username, this.password));
}
@Override
public void setUsername(final String username) {
this.username = username;
final UrlResource serviceUrl = getServiceUrl();
if (serviceUrl != null) {
setServiceUrl(serviceUrl.newUrlResourceAuthorization(this.username, this.password));
}
}
@Override
public MapEx toMap() {
final MapEx map = newTypeMap(J_TYPE);
final String name = getName();
addToMap(map, "name", name);
map.put("serviceUrl", getServiceUrl());
addToMap(map, "username", this.username, "");
addToMap(map, "password", PasswordUtil.encrypt(this.password), "");
return map;
}
@Override
public String toString() {
return getName() + " " + getServiceUrl();
}
}