Java tutorial
/* (c) 2014 Boundless, http://boundlessgeo.com * This code is licensed under the GPL 2.0 license. */ package com.boundlessgeo.geoserver.api.controllers; import static org.geoserver.catalog.Predicates.and; import static org.geoserver.catalog.Predicates.equal; import java.io.File; import java.io.IOException; import java.io.Serializable; import java.net.URL; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nullable; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang.WordUtils; import org.geoserver.catalog.CascadeDeleteVisitor; import org.geoserver.catalog.Catalog; import org.geoserver.catalog.CatalogFactory; import org.geoserver.catalog.CoverageInfo; import org.geoserver.catalog.CoverageStoreInfo; import org.geoserver.catalog.DataStoreInfo; import org.geoserver.catalog.FeatureTypeInfo; import org.geoserver.catalog.LayerInfo; import org.geoserver.catalog.ResourceInfo; import org.geoserver.catalog.ResourcePool; import org.geoserver.catalog.StoreInfo; import org.geoserver.catalog.WMSStoreInfo; import org.geoserver.catalog.WorkspaceInfo; import org.geoserver.catalog.util.CloseableIterator; import org.geoserver.config.GeoServer; import org.geoserver.ows.URLMangler.URLType; import org.geoserver.platform.GeoServerResourceLoader; import org.geoserver.platform.resource.Files; import org.geoserver.platform.resource.Paths; import org.geotools.coverage.grid.io.AbstractGridFormat; import org.geotools.coverage.grid.io.GridCoverage2DReader; import org.geotools.data.DataAccess; import org.geotools.data.DataAccessFinder; import org.geotools.data.DataStore; import org.geotools.data.ows.Layer; import org.geotools.data.wms.WebMapServer; import org.geotools.feature.NameImpl; import org.geotools.util.Converters; import org.geotools.util.NullProgressListener; import org.geotools.util.logging.Logging; import org.opengis.coverage.grid.Format; import org.opengis.coverage.grid.GridCoverageReader; import org.opengis.feature.type.FeatureType; import org.opengis.feature.type.Name; import org.opengis.filter.Filter; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import com.boundlessgeo.geoserver.json.JSONArr; import com.boundlessgeo.geoserver.json.JSONObj; import com.google.common.base.Function; import com.google.common.collect.Iterables; /** * Used to connect to data storage (file, database, or service). * <p> * This API is locked down for map composer and is (not intended to be stable between releases).</p> * * @see <a href="https://github.com/boundlessgeo/suite/wiki/Stores-API">Store API</a> (Wiki) */ @Controller @RequestMapping("/api/stores") public class StoreController extends ApiController { static Logger LOG = Logging.getLogger(StoreController.class); @Autowired public StoreController(GeoServer geoServer) { super(geoServer); } @RequestMapping(value = "/{wsName}", method = RequestMethod.GET) public @ResponseBody JSONArr list(@PathVariable String wsName) { JSONArr arr = new JSONArr(); Catalog cat = geoServer.getCatalog(); for (StoreInfo store : cat.getStoresByWorkspace(wsName, StoreInfo.class)) { store(arr.addObject(), store); } return arr; } @RequestMapping(value = "/{wsName}/{name}", method = RequestMethod.GET) public @ResponseBody JSONObj get(@PathVariable String wsName, @PathVariable String name, HttpServletRequest req) { StoreInfo store = findStore(wsName, name, geoServer.getCatalog()); if (store == null) { throw new IllegalArgumentException("Store " + wsName + ":" + name + " not found"); } try { return storeDetails(new JSONObj(), store, req); } catch (IOException e) { throw new RuntimeException(String.format("Error occured accessing store: %s,%s", wsName, name), e); } } @RequestMapping(value = "/{wsName}/{stName}/{name}", method = RequestMethod.GET) public @ResponseBody JSONObj resource(@PathVariable String wsName, @PathVariable String stName, @PathVariable String name, HttpServletRequest req) throws IOException { Catalog cat = geoServer.getCatalog(); StoreInfo store = findStore(wsName, stName, cat); JSONObj obj = resource(new JSONObj(), store, name, req); obj.putObject("store").put("name", stName).put("url", IO.url(req, "/stores/%s/%s", wsName, stName)); return obj; } @RequestMapping(value = "/{wsName}/{name}", method = RequestMethod.DELETE) public @ResponseBody JSONObj delete(@PathVariable String wsName, @PathVariable String name, @RequestParam(value = "recurse", defaultValue = "false") boolean recurse, HttpServletRequest req) { StoreInfo store = findStore(wsName, name, geoServer.getCatalog()); Catalog cat = geoServer.getCatalog(); List<ResourceInfo> layers = cat.getResourcesByStore(store, ResourceInfo.class); if (layers.isEmpty()) { cat.remove(store); } else if (recurse) { CascadeDeleteVisitor delete = new CascadeDeleteVisitor(cat); if (store instanceof DataStoreInfo) { delete.visit((DataStoreInfo) store); } else if (store instanceof CoverageStoreInfo) { delete.visit((CoverageStoreInfo) store); } else if (store instanceof WMSStoreInfo) { delete.visit((WMSStoreInfo) store); } else { throw new IllegalStateException( "Unable to delete " + name + " - expected data store, coverage store or wms store"); } } else { StringBuilder message = new StringBuilder(); message.append("Use recurse=true to remove ").append(name).append(" along with layers:"); for (ResourceInfo l : layers) { message.append(' ').append(l.getName()); } throw new IllegalStateException(message.toString()); } JSONObj json = new JSONObj(); json.put("name", name).put("workspace", wsName); return json; } @SuppressWarnings({ "rawtypes", "unchecked" }) @RequestMapping(value = "/{wsName}/{name}", method = RequestMethod.POST) public @ResponseBody JSONObj create(@PathVariable String wsName, @PathVariable String name, @RequestBody JSONObj obj, HttpServletRequest req) throws IOException { Catalog cat = geoServer.getCatalog(); CatalogFactory factory = cat.getFactory(); WorkspaceInfo workspace = findWorkspace(wsName); StoreInfo store = null; JSONObj params = obj.object("connection"); if (params == null) { throw new IllegalArgumentException("connection parameters required"); } if (params.has("raster")) { String url = params.str("raster"); CoverageStoreInfo info = factory.createCoverageStore(); info.setWorkspace(workspace); info.setType(name); // connect and defaults info.setURL(url); info.setType(obj.str("type")); try { GridCoverageReader reader = info.getGridCoverageReader(null, null); Format format = reader.getFormat(); info.setDescription(format.getDescription()); info.setEnabled(true); } catch (IOException e) { info.setError(e); info.setEnabled(false); } store = info; } else if (params.has("url") && params.str("url").toLowerCase().contains("Service=WMS") && params.str("url").startsWith("http")) { WMSStoreInfo info = factory.createWebMapServer(); info.setWorkspace(workspace); info.setType(name); // connect and defaults info.setCapabilitiesURL(params.str("url")); try { WebMapServer service = info.getWebMapServer(new NullProgressListener()); info.setDescription(service.getInfo().getDescription()); info.setEnabled(true); } catch (Throwable e) { info.setError(e); info.setEnabled(false); } store = info; } else { HashMap map = new HashMap(params.raw()); Map resolved = ResourcePool.getParams(map, cat.getResourceLoader()); DataAccess dataStore = DataAccessFinder.getDataStore(resolved); if (dataStore == null) { throw new IllegalArgumentException( "Connection parameters incomplete (does not match an available data store, coverage store or wms store)."); } DataStoreInfo info = factory.createDataStore(); info.setWorkspace(workspace); info.setType(name); info.getConnectionParameters().putAll(map); try { info.setDescription(dataStore.getInfo().getDescription()); info.setEnabled(true); } catch (Throwable e) { info.setError(e); info.setEnabled(false); } store = info; } boolean refresh = define(store, obj); if (refresh) { LOG.log(Level.FINE, "Inconsistent: default connection used for store creation required refresh"); } cat.add(store); return storeDetails(new JSONObj(), store, req); } @RequestMapping(value = "/{wsName}/{name}", method = RequestMethod.PATCH) public @ResponseBody JSONObj patch(@PathVariable String wsName, @PathVariable String name, @RequestBody JSONObj obj, HttpServletRequest req) throws IOException { Catalog cat = geoServer.getCatalog(); StoreInfo store = cat.getStoreByName(wsName, name, StoreInfo.class); boolean refresh = define(store, obj); cat.save(store); if (refresh) { resetConnection(store); } return storeDetails(new JSONObj(), store, req); } private void resetConnection(StoreInfo store) { Catalog cat = geoServer.getCatalog(); if (store instanceof CoverageStoreInfo) { cat.getResourcePool().clear((CoverageStoreInfo) store); } else if (store instanceof DataStoreInfo) { cat.getResourcePool().clear((DataStoreInfo) store); } else if (store instanceof WMSStoreInfo) { cat.getResourcePool().clear((WMSStoreInfo) store); } } @RequestMapping(value = "/{wsName}/{name}", method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_VALUE) public @ResponseBody JSONObj put(@PathVariable String wsName, @PathVariable String name, @RequestBody JSONObj obj, HttpServletRequest req) throws IOException { Catalog cat = geoServer.getCatalog(); StoreInfo store = cat.getStoreByName(wsName, name, StoreInfo.class); // pending: clear store to defaults boolean refresh = define(store, obj); cat.save(store); if (refresh) { resetConnection(store); } return storeDetails(new JSONObj(), store, req); } @SuppressWarnings("unchecked") private boolean define(StoreInfo store, JSONObj obj) { boolean reconnect = false; for (String prop : obj.keys()) { if ("description".equals(prop)) { store.setDescription(obj.str(prop)); } else if ("enabled".equals(prop)) { store.setEnabled(obj.bool(prop)); reconnect = true; } else if ("name".equals(prop)) { store.setName(obj.str(prop)); } else if ("workspace".equals(prop)) { WorkspaceInfo newWorkspace = findWorkspace(obj.str(prop)); store.setWorkspace(newWorkspace); } else if (store instanceof CoverageStoreInfo) { CoverageStoreInfo info = (CoverageStoreInfo) store; if ("connection".equals(prop)) { JSONObj connection = obj.object(prop); if (!connection.has("raster") && connection.str("raster") != null) { throw new IllegalArgumentException( "Property connection.raster required for coverage store"); } for (String param : connection.keys()) { if ("raster".equals(param)) { String url = connection.str(param); reconnect = reconnect || url == null || !url.equals(info.getURL()); info.setURL(url); } } } } else if (store instanceof WMSStoreInfo) { WMSStoreInfo info = (WMSStoreInfo) store; if ("connection".equals(prop)) { JSONObj connection = obj.object(prop); if (!connection.has("url") && connection.str("url") != null) { throw new IllegalArgumentException("Property connection.url required for wms store"); } for (String param : connection.keys()) { if ("url".equals(param)) { String url = connection.str(param); reconnect = reconnect || url == null || !url.equals(info.getCapabilitiesURL()); info.setCapabilitiesURL(url); } } } } if (store instanceof DataStoreInfo) { DataStoreInfo info = (DataStoreInfo) store; if ("connection".equals(prop)) { JSONObj connection = obj.object(prop); info.getConnectionParameters().clear(); info.getConnectionParameters().putAll(connection.raw()); reconnect = true; } } } return reconnect; } private JSONObj store(JSONObj obj, StoreInfo store) { String name = store.getName(); obj.put("name", name).put("workspace", store.getWorkspace().getName()).put("enabled", store.isEnabled()) .put("description", store.getDescription()).put("format", store.getType()); String source = source(store); obj.put("source", source).put("type", IO.Kind.of(store).name()).put("kind", IO.Type.of(store).name()); return IO.metadata(obj, store); } private JSONObj storeDetails(JSONObj json, StoreInfo store, HttpServletRequest req) throws IOException { store(json, store); JSONObj connection = new JSONObj(); Map<String, Serializable> params = store.getConnectionParameters(); for (Entry<String, Serializable> param : params.entrySet()) { String key = param.getKey(); Object value = param.getValue(); String text = value == null ? null : value.toString(); connection.put(key, text); } if (store instanceof CoverageStoreInfo) { CoverageStoreInfo info = (CoverageStoreInfo) store; connection.put("raster", info.getURL()); } if (store instanceof WMSStoreInfo) { WMSStoreInfo info = (WMSStoreInfo) store; json.put("wms", info.getCapabilitiesURL()); } json.put("connection", connection); json.put("error", IO.error(new JSONObj(), store.getError())); if (store.isEnabled()) { resources(store, json.putArray("resources"), req); } json.put("layer-count", layerCount(store)); return json; } int layerCount(StoreInfo store) throws IOException { Catalog cat = geoServer.getCatalog(); WorkspaceInfo ws = store.getWorkspace(); Filter filter = and(equal("store", store), equal("namespace.prefix", ws.getName())); int count = 0; try (CloseableIterator<ResourceInfo> layers = cat.list(ResourceInfo.class, filter);) { while (layers.hasNext()) { ResourceInfo r = layers.next(); for (LayerInfo l : cat.getLayers(r)) { if (l != null) { count++; } } } } return count; } private JSONArr layers(ResourceInfo r, JSONArr list) throws IOException { if (r != null) { Catalog cat = geoServer.getCatalog(); for (LayerInfo l : cat.getLayers(r)) { JSONObj obj = layer(list.addObject(), l, true); } } return list; } private JSONArr layers(StoreInfo store, JSONArr list) throws IOException { Catalog cat = geoServer.getCatalog(); WorkspaceInfo ws = store.getWorkspace(); Filter filter = and(equal("store", store), equal("namespace.prefix", ws.getName())); try (CloseableIterator<ResourceInfo> layers = cat.list(ResourceInfo.class, filter);) { while (layers.hasNext()) { ResourceInfo r = layers.next(); for (LayerInfo l : cat.getLayers(r)) { layer(list.addObject(), l, true); } } } return list; } @SuppressWarnings("unchecked") private JSONArr resources(StoreInfo store, JSONArr list, HttpServletRequest req) throws IOException { Catalog cat = geoServer.getCatalog(); WorkspaceInfo ws = store.getWorkspace(); for (String resource : listResources(store)) { resource(list.addObject(), store, resource, req); } return list; } private JSONObj resource(JSONObj obj, StoreInfo store, String name, HttpServletRequest req) throws IOException { obj.put("name", name); if (store instanceof DataStoreInfo) { DataStoreInfo data = (DataStoreInfo) store; @SuppressWarnings("rawtypes") DataAccess dataStore = data.getDataStore(new NullProgressListener()); FeatureType schema; org.geotools.data.ResourceInfo info; if (dataStore instanceof DataStore) { schema = ((DataStore) dataStore).getSchema(name); info = ((DataStore) dataStore).getFeatureSource(name).getInfo(); } else { NameImpl qname = new NameImpl(name); schema = dataStore.getSchema(qname); info = dataStore.getFeatureSource(qname).getInfo(); } String title = info.getTitle() == null ? WordUtils.capitalize(name) : info.getTitle(); String description = info.getDescription() == null ? "" : info.getDescription(); obj.put("title", title); obj.put("description", description); JSONArr keywords = obj.putArray("keywords"); keywords.raw().addAll(info.getKeywords()); IO.bounds(obj.putObject("bounds"), info.getBounds()); IO.schema(obj.putObject("schema"), schema, false); } if (store instanceof CoverageStoreInfo) { CoverageStoreInfo data = (CoverageStoreInfo) store; GridCoverageReader r = data.getGridCoverageReader(null, null); obj.put("title", WordUtils.capitalize(name)); obj.put("description", ""); if (r instanceof GridCoverage2DReader) { GridCoverage2DReader reader = (GridCoverage2DReader) r; CoordinateReferenceSystem crs = reader.getCoordinateReferenceSystem(name); IO.schemaGrid(obj.putObject("schema"), crs, false); } else { IO.schemaGrid(obj.putObject("schema"), AbstractGridFormat.getDefaultCRS(), false); } } JSONArr layers = obj.putArray("layers"); Catalog cat = geoServer.getCatalog(); if (store instanceof CoverageStoreInfo) { // coverage store does not respect native name so we search by id for (CoverageInfo info : cat.getCoveragesByCoverageStore((CoverageStoreInfo) store)) { layers(info, layers); } } else { Filter filter = and(equal("namespace.prefix", store.getWorkspace().getName()), equal("nativeName", name)); try (CloseableIterator<ResourceInfo> published = cat.list(ResourceInfo.class, filter);) { while (published.hasNext()) { ResourceInfo info = published.next(); if (!info.getStore().getId().equals(store.getId())) { continue; // native name is not enough, double check store id } layers(info, layers); } } } return obj; } private JSONObj resource(JSONObj json, ResourceInfo info, boolean details) { json.put("name", info.getName()).put("workspace", info.getStore().getWorkspace().getName()); if (details) { if (info instanceof FeatureTypeInfo) { FeatureTypeInfo data = (FeatureTypeInfo) info; try { IO.schema(json.putObject("schema"), data.getFeatureType(), false); } catch (IOException e) { } } else if (info instanceof CoverageInfo) { CoverageInfo data = (CoverageInfo) info; IO.schemaGrid(json.putObject("schema"), data, false); } } return json; } private JSONObj layer(JSONObj json, LayerInfo info, boolean details) { if (details) { IO.layer(json, info, null); // Todo add URL } else { json.put("name", info.getName()).put("workspace", info.getResource().getStore().getWorkspace().getName()); } return json; } private Iterable<String> listResources(StoreInfo store) throws IOException { if (store instanceof DataStoreInfo) { return Iterables.transform(((DataStoreInfo) store).getDataStore(null).getNames(), new Function<Name, String>() { @Nullable @Override public String apply(@Nullable Name input) { return input.getLocalPart(); } }); } else if (store instanceof CoverageStoreInfo) { return Arrays .asList(((CoverageStoreInfo) store).getGridCoverageReader(null, null).getGridCoverageNames()); } else if (store instanceof WMSStoreInfo) { return Iterables.transform( ((WMSStoreInfo) store).getWebMapServer(null).getCapabilities().getLayerList(), new Function<Layer, String>() { @Nullable @Override public String apply(@Nullable Layer input) { return input.getName(); } }); } throw new IllegalStateException("Unrecognized store type"); } private String source(StoreInfo store) { if (store instanceof CoverageStoreInfo) { CoverageStoreInfo coverage = (CoverageStoreInfo) store; return sourceURL(coverage.getURL()); } GeoServerResourceLoader resourceLoader = geoServer.getCatalog().getResourceLoader(); Map<String, Serializable> params = ResourcePool.getParams(store.getConnectionParameters(), resourceLoader); if (params.containsKey("dbtype")) { // See JDBCDataStoreFactory for details String host = Converters.convert(params.get("host"), String.class); String port = Converters.convert(params.get("port"), String.class); String dbtype = Converters.convert(params.get("dbtype"), String.class); String schema = Converters.convert(params.get("schema"), String.class); String database = Converters.convert(params.get("database"), String.class); StringBuilder source = new StringBuilder(); source.append(host); if (port != null) { source.append(':').append(port); } source.append('/').append(dbtype).append('/').append(database); if (schema != null) { source.append('/').append(schema); } return source.toString(); } else if (store instanceof WMSStoreInfo) { String url = ((WMSStoreInfo) store).getCapabilitiesURL(); return url; } else if (params.keySet().contains("directory")) { String directory = Converters.convert(params.get("directory"), String.class); return sourceFile(directory); } else if (params.keySet().contains("file")) { String file = Converters.convert(params.get("file"), String.class); return sourceFile(file); } if (params.containsKey("url")) { String url = Converters.convert(params.get("url"), String.class); return sourceURL(url); } for (Object value : params.values()) { if (value instanceof URL) { return source((URL) value); } if (value instanceof File) { return source((File) value); } if (value instanceof String) { String text = (String) value; if (text.startsWith("file:")) { return sourceURL(text); } else if (text.startsWith("http:") || text.startsWith("https:") || text.startsWith("ftp:")) { return text; } } } return "undertermined"; } private String source(File file) { File baseDirectory = dataDir().getResourceLoader().getBaseDirectory(); return file.isAbsolute() ? file.toString() : Paths.convert(baseDirectory, file); } private String source(URL url) { File baseDirectory = dataDir().getResourceLoader().getBaseDirectory(); if (url.getProtocol().equals("file")) { File file = Files.url(baseDirectory, url.toExternalForm()); if (file != null && !file.isAbsolute()) { return Paths.convert(baseDirectory, file); } } return url.toExternalForm(); } private String sourceURL(String url) { File baseDirectory = dataDir().getResourceLoader().getBaseDirectory(); File file = Files.url(baseDirectory, url); if (file != null) { return Paths.convert(baseDirectory, file); } return url; } private String sourceFile(String file) { File baseDirectory = dataDir().getResourceLoader().getBaseDirectory(); File f = new File(file); return f.isAbsolute() ? file : Paths.convert(baseDirectory, f); } private WorkspaceInfo findWorkspace(String wsName) { Catalog cat = geoServer.getCatalog(); WorkspaceInfo ws; if ("default".equals(wsName)) { ws = cat.getDefaultWorkspace(); } else { ws = cat.getWorkspaceByName(wsName); } if (ws == null) { throw new RuntimeException("Unable to find workspace " + wsName); } return ws; } }