/* (c) 2017 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.rest.catalog;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import org.geoserver.catalog.CascadeDeleteVisitor;
import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.CatalogBuilder;
import org.geoserver.catalog.CatalogInfo;
import org.geoserver.catalog.CoverageStoreInfo;
import org.geoserver.catalog.WorkspaceInfo;
import org.geoserver.config.util.XStreamPersister;
import org.geoserver.rest.ResourceNotFoundException;
import org.geoserver.rest.RestBaseController;
import org.geoserver.rest.RestException;
import org.geoserver.rest.converters.XStreamMessageConverter;
import org.geoserver.rest.util.MediaTypeExtensions;
import org.geoserver.rest.wrapper.RestWrapper;
import org.geotools.coverage.grid.io.StructuredGridCoverage2DReader;
import org.geotools.util.logging.Logging;
import org.opengis.coverage.grid.GridCoverageReader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
/**
* Coverage store controller
*/
@RestController
@ControllerAdvice
@RequestMapping(path = RestBaseController.ROOT_PATH+"/workspaces/{workspaceName}/coveragestores")
public class CoverageStoreController extends AbstractCatalogController {
private static final Logger LOGGER = Logging.getLogger(CoverageStoreController.class);
@Autowired
public CoverageStoreController(@Qualifier("catalog") Catalog catalog) {
super(catalog);
}
@GetMapping(produces = {
MediaType.APPLICATION_JSON_VALUE,
MediaType.APPLICATION_XML_VALUE,
MediaType.TEXT_HTML_VALUE })
public RestWrapper<CoverageStoreInfo> coverageStoresGet(@PathVariable String workspaceName) {
WorkspaceInfo ws = catalog.getWorkspaceByName(workspaceName);
if(ws == null) {
throw new ResourceNotFoundException("No such workspace : " + workspaceName);
}
List<CoverageStoreInfo> coverageStores = catalog
.getCoverageStoresByWorkspace(ws);
return wrapList(coverageStores, CoverageStoreInfo.class);
}
@GetMapping(path = "{storeName}", produces = {
MediaType.APPLICATION_JSON_VALUE,
MediaType.APPLICATION_XML_VALUE,
MediaType.TEXT_HTML_VALUE })
public RestWrapper<CoverageStoreInfo> coverageStoreGet(
@PathVariable String workspaceName,
@PathVariable String storeName) {
CoverageStoreInfo coverageStore = getExistingCoverageStore(workspaceName, storeName);
return wrapObject(coverageStore, CoverageStoreInfo.class);
}
@PostMapping(consumes = {
MediaType.APPLICATION_JSON_VALUE,
MediaTypeExtensions.TEXT_JSON_VALUE,
MediaType.APPLICATION_XML_VALUE,
MediaType.TEXT_XML_VALUE })
public ResponseEntity<String> coverageStorePost(
@RequestBody CoverageStoreInfo coverageStore,
@PathVariable String workspaceName,
UriComponentsBuilder builder) {
catalog.validate(coverageStore, true).throwIfInvalid();
catalog.add(coverageStore);
String storeName = coverageStore.getName();
LOGGER.info("POST coverage store " + storeName);
UriComponents uriComponents = builder.path("/workspaces/{workspaceName}/coveragestores/{storeName}")
.buildAndExpand(workspaceName, storeName);
HttpHeaders headers = new HttpHeaders();
headers.setLocation(uriComponents.toUri());
return new ResponseEntity<>(storeName, headers, HttpStatus.CREATED);
}
@PutMapping(value = "{storeName}", consumes = {
MediaType.APPLICATION_JSON_VALUE,
MediaTypeExtensions.TEXT_JSON_VALUE,
MediaType.APPLICATION_XML_VALUE,
MediaType.TEXT_XML_VALUE })
public void coverageStoreGet(
@RequestBody CoverageStoreInfo info,
@PathVariable String workspaceName,
@PathVariable String storeName) {
CoverageStoreInfo original = getExistingCoverageStore(workspaceName, storeName);
new CatalogBuilder(catalog).updateCoverageStore(original, info);
catalog.validate(original, false).throwIfInvalid();
catalog.save(original);
clear(original);
LOGGER.info("PUT coverage store " + workspaceName + "," + storeName);
}
private CoverageStoreInfo getExistingCoverageStore(String workspaceName, String storeName) {
CoverageStoreInfo original = catalog.getCoverageStoreByName(workspaceName, storeName);
if(original == null) {
throw new ResourceNotFoundException(
"No such coverage store: " + workspaceName + "," + storeName);
}
return original;
}
@DeleteMapping(value = "{storeName}")
public void coverageStoreDelete(
@PathVariable String workspaceName,
@PathVariable String storeName,
@RequestParam(name = "recurse", required = false, defaultValue = "false") boolean recurse,
@RequestParam(name = "purge", required = false, defaultValue = "none") String deleteType) throws IOException {
CoverageStoreInfo cs = getExistingCoverageStore(workspaceName, storeName);
if (!recurse) {
if (!catalog.getCoveragesByCoverageStore(cs).isEmpty()) {
throw new RestException("coveragestore not empty", HttpStatus.UNAUTHORIZED);
}
catalog.remove(cs);
} else {
new CascadeDeleteVisitor(catalog).visit(cs);
}
delete(deleteType, cs);
clear(cs);
LOGGER.info("DELETE coverage store " + workspaceName + ":s" + workspaceName);
}
/**
* Check the deleteType parameter in order to decide whether to delete some data too (all, or just metadata).
* @param deleteType
* @param cs
* @throws IOException
*/
private void delete(String deleteType, CoverageStoreInfo cs) throws IOException {
if (!deleteType.equalsIgnoreCase("none") &&
(deleteType.equalsIgnoreCase("all") || deleteType.equalsIgnoreCase("metadata"))) {
final boolean deleteData = deleteType.equalsIgnoreCase("all");
GridCoverageReader reader = cs.getGridCoverageReader(null, null);
if (reader instanceof StructuredGridCoverage2DReader) {
((StructuredGridCoverage2DReader) reader).delete(deleteData);
}
}
}
void clear(CoverageStoreInfo info) {
catalog.getResourcePool().clear(info);
}
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return CoverageStoreInfo.class.isAssignableFrom(methodParameter.getParameterType());
}
@Override
public void configurePersister(XStreamPersister persister, XStreamMessageConverter converter) {
persister.setCallback(new XStreamPersister.Callback() {
@Override
protected Class<CoverageStoreInfo> getObjectClass() {
return CoverageStoreInfo.class;
}
@Override
protected CatalogInfo getCatalogObject() {
Map<String, String> uriTemplateVars = (Map<String, String>) RequestContextHolder.getRequestAttributes().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
String workspace = uriTemplateVars.get("workspaceName");
String coveragestore = uriTemplateVars.get("storeName");
if (workspace == null || coveragestore == null) {
return null;
}
return catalog.getCoverageStoreByName(workspace, coveragestore);
}
@Override
protected void postEncodeCoverageStore(CoverageStoreInfo cs,
HierarchicalStreamWriter writer, MarshallingContext context) {
// add a link to the coverages
writer.startNode("coverages");
converter.encodeCollectionLink("coverages", writer);
writer.endNode();
}
@Override
protected void postEncodeReference(Object obj, String ref, String prefix,
HierarchicalStreamWriter writer, MarshallingContext context) {
if (obj instanceof WorkspaceInfo) {
converter.encodeLink("/workspaces/" + converter.encode(ref), writer);
}
}
});
}
}