/* (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.CoverageInfo; import org.geoserver.catalog.FeatureTypeInfo; import org.geoserver.catalog.LayerInfo; import org.geoserver.catalog.ResourceInfo; import org.geoserver.catalog.StyleInfo; import org.geoserver.catalog.WMSLayerInfo; import org.geoserver.config.util.XStreamPersister; import org.geoserver.rest.ResourceNotFoundException; import org.geoserver.rest.RestBaseController; import org.geoserver.rest.converters.XStreamMessageConverter; import org.geoserver.rest.wrapper.RestWrapper; import org.geotools.util.logging.Logging; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; 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.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 com.thoughtworks.xstream.converters.MarshallingContext; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; /** * Controller for managing GeoServer Layers. */ @RestController @ControllerAdvice @RequestMapping(path = RestBaseController.ROOT_PATH+"/layers") public class LayerController extends AbstractCatalogController { private static final Logger LOGGER = Logging.getLogger(LayerController.class); @Autowired public LayerController(@Qualifier("catalog") Catalog catalog) { super(catalog); } /** * All layers as JSON, XML or HTML. * * @return All layers */ @GetMapping(produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_HTML_VALUE }) public RestWrapper<LayerInfo> layersGet() { List<LayerInfo> layers = catalog.getLayers(); return wrapList(layers, LayerInfo.class); } /** * A single layer as JSON, XML or HTML. * * @param layerName * @return A single layer */ @GetMapping(path = "/{layerName}", produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_HTML_VALUE }) public RestWrapper<LayerInfo> layerGet( @PathVariable String layerName, @RequestParam (name = "quietOnNotFound", required = false) Boolean quietOnNotFound) { LayerInfo layer = catalog.getLayerByName(layerName); return wrapObject(layer, LayerInfo.class, "No such layer: "+layerName, quietOnNotFound ); } @DeleteMapping(value = "/{layerName}") public void layerDelete( @PathVariable String layerName, @RequestParam(name = "recurse", required = false, defaultValue = "false") boolean recurse) throws IOException { LayerInfo layer = catalog.getLayerByName(layerName); if(layer == null) { throw new ResourceNotFoundException(layerName); } if (!recurse) { catalog.remove(layer); LOGGER.info( "DELETE layer '" + layerName+"'"); } else { new CascadeDeleteVisitor(catalog).visit(layer); LOGGER.info( "DELETE layer '" + layerName + "' recurse=true"); } } @PutMapping(value = "/{layerName}") public void layerPut(@RequestBody LayerInfo layer,@PathVariable String layerName) { LayerInfo original = catalog.getLayerByName(layerName); // ensure this is not a name change // TODO: Uncomment this when the resource/layer split is not, now by definition // we cannot rename a layer, it's just not possible and it's not un-marshalled either // if ( layer.getName() != null && !layer.getName().equals( original.getName() ) ) { // throw new RestletException( "Can't change name of a layer", Status.CLIENT_ERROR_FORBIDDEN ); // } // force in the same resource otherwise the update will simply fail as we cannot reach the name layer.setResource(original.getResource()); CatalogBuilder session = new CatalogBuilder( catalog ); session.updateLayer( original, layer ); catalog.save( original ); LOGGER.info( "PUT layer " + layerName); } public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { return LayerInfo.class.isAssignableFrom(methodParameter.getParameterType()); } // // Configuration and Settings // public void configurePersister(XStreamPersister persister, XStreamMessageConverter converter) { persister.setCallback(new XStreamPersister.Callback() { @Override protected Class<LayerInfo> getObjectClass() { return LayerInfo.class; } @Override protected CatalogInfo getCatalogObject() { @SuppressWarnings("unchecked") Map<String, String> uriTemplateVars = (Map<String, String>) RequestContextHolder .getRequestAttributes() .getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST); String layerName = uriTemplateVars.get("layerName"); if (layerName == null) { return null; } return catalog.getLayerByName(layerName); } @Override protected void postEncodeReference(Object obj, String ref, String prefix, HierarchicalStreamWriter writer, MarshallingContext context) { if (obj instanceof StyleInfo) { StyleInfo style = (StyleInfo) obj; StringBuilder link = new StringBuilder(); if (style.getWorkspace() != null) { String wsName = style.getWorkspace().getName(); writer.startNode("workspace"); writer.setValue(wsName); writer.endNode(); link.append("/workspaces/").append(converter.encode(wsName)); } link.append("/styles/").append(converter.encode(style.getName())); converter.encodeLink(link.toString(), writer); } if (obj instanceof ResourceInfo) { ResourceInfo r = (ResourceInfo) obj; StringBuilder link = new StringBuilder("/workspaces/") .append(converter.encode(r.getStore().getWorkspace().getName())).append("/"); if (r instanceof FeatureTypeInfo) { link.append("datastores/").append(converter.encode(r.getStore().getName())) .append("/featuretypes/"); } else if (r instanceof CoverageInfo) { link.append("coveragestores/").append(converter.encode(r.getStore().getName())) .append("/coverages/"); } else if (r instanceof WMSLayerInfo) { link.append("wmsstores/").append(converter.encode(r.getStore().getName())) .append("/wmslayers/"); } else { return; } link.append(converter.encode(r.getName())); converter.encodeLink(link.toString(), writer); } } }); } }