/* (c) 2014 - 2016 Open Source Geospatial Foundation - all rights reserved * (c) 2001 - 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.rest.catalog; import org.geoserver.catalog.Catalog; import org.geoserver.catalog.CatalogBuilder; import org.geoserver.catalog.CatalogFacade; import org.geoserver.catalog.CatalogInfo; import org.geoserver.catalog.LayerGroupInfo; import org.geoserver.catalog.LayerInfo; import org.geoserver.catalog.StyleInfo; 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.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.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.*; 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; import java.lang.reflect.Type; import java.util.List; import java.util.Map; import java.util.logging.Logger; /** * Coverage store controller */ @RestController @ControllerAdvice @RequestMapping( path = { RestBaseController.ROOT_PATH + "/layergroups", RestBaseController.ROOT_PATH + "/workspaces/{workspaceName}/layergroups"}, produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_HTML_VALUE}) public class LayerGroupController extends AbstractCatalogController { private static final Logger LOGGER = Logging.getLogger(LayerGroupController.class); @Autowired public LayerGroupController(@Qualifier("catalog") Catalog catalog) { super(catalog); } @GetMapping public RestWrapper<?> getLayerGroups(@PathVariable(required = false) String workspaceName) { if(workspaceName != null && catalog.getWorkspaceByName(workspaceName) == null) { throw new ResourceNotFoundException("Workspace " + workspaceName + " not found"); } List<LayerGroupInfo> layerGroupInfos = workspaceName != null ? catalog.getLayerGroupsByWorkspace(workspaceName) : catalog.getLayerGroupsByWorkspace(CatalogFacade.NO_WORKSPACE); return wrapList(layerGroupInfos, LayerGroupInfo.class); } @GetMapping(value = "{layerGroupName}") public RestWrapper<?> getLayerGroup( @PathVariable String layerGroupName, @PathVariable(required = false) String workspaceName, @RequestParam(name = "quietOnNotFound", required = false) Boolean quietOnNotFound) { if(workspaceName != null && catalog.getWorkspaceByName(workspaceName) == null) { throw new ResourceNotFoundException("Workspace " + workspaceName + " not found"); } LayerGroupInfo layerGroupInfo = workspaceName != null ? catalog.getLayerGroupByName(workspaceName, layerGroupName) : catalog.getLayerGroupByName(layerGroupName); String errorMessage = "No such layer group " + layerGroupName + (workspaceName == null ? "" : " in workspace " + workspaceName); return wrapObject(layerGroupInfo, LayerGroupInfo.class, errorMessage, quietOnNotFound); } @PostMapping(consumes = { MediaType.APPLICATION_JSON_VALUE, MediaTypeExtensions.TEXT_JSON_VALUE, MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE}) public ResponseEntity<String> layerGroupPost( @RequestBody LayerGroupInfo lg, @PathVariable(required = false) String workspaceName, UriComponentsBuilder builder) throws Exception { if(workspaceName != null && catalog.getWorkspaceByName(workspaceName) == null) { throw new ResourceNotFoundException("Workspace " + workspaceName + " not found"); } checkFullAdminRequired(workspaceName); if ( lg.getLayers().isEmpty() ) { throw new RestException( "layer group must not be empty", HttpStatus.BAD_REQUEST ); } if ( lg.getBounds() == null ) { LOGGER.fine( "Auto calculating layer group bounds"); new CatalogBuilder( catalog ).calculateLayerGroupBounds(lg); } if (workspaceName != null) { lg.setWorkspace(catalog.getWorkspaceByName(workspaceName)); } if (lg.getMode() == null) { LOGGER.fine("Setting layer group mode SINGLE"); lg.setMode(LayerGroupInfo.Mode.SINGLE); } catalog.validate(lg, true).throwIfInvalid(); catalog.add(lg); String layerGroupName = lg.getName(); LOGGER.info("POST layer group " + layerGroupName); UriComponents uriComponents = builder.path("/layergroups/{layerGroupName}") .buildAndExpand(layerGroupName); HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.setLocation(uriComponents.toUri()); return new ResponseEntity<>(layerGroupName, httpHeaders, HttpStatus.CREATED); } @PutMapping(value = "{layerGroupName}", consumes = { MediaType.APPLICATION_JSON_VALUE, MediaTypeExtensions.TEXT_JSON_VALUE, MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE}) public void layerGroupPut( @RequestBody LayerGroupInfo lg, @PathVariable(required = false) String workspaceName, @PathVariable String layerGroupName) throws Exception { if(workspaceName != null && catalog.getWorkspaceByName(workspaceName) == null) { throw new ResourceNotFoundException("Workspace " + workspaceName + " not found"); } checkFullAdminRequired(workspaceName); LOGGER.info( "PUT layer group " + layerGroupName + (workspaceName != null ? ", workspace " + workspaceName : "")); LayerGroupInfo original = workspaceName != null ? catalog.getLayerGroupByName(workspaceName, layerGroupName) : catalog.getLayerGroupByName(layerGroupName); //ensure not a name change if ( lg.getName() != null && !lg.getName().equals( original.getName() ) ) { throw new RestException( "Can't change name of a layer group", HttpStatus.FORBIDDEN ); } //ensure not a workspace change if (lg.getWorkspace() != null) { if (!lg.getWorkspace().equals(original.getWorkspace())) { throw new RestException( "Can't change the workspace of a layer group, instead " + "DELETE from existing workspace and POST to new workspace", HttpStatus.FORBIDDEN ); } } new CatalogBuilder( catalog ).updateLayerGroup( original, lg ); catalog.save( original ); } @DeleteMapping(value = "{layerGroupName}") public void layerGroupDelete( @PathVariable(required = false) String workspaceName, @PathVariable String layerGroupName) { if(workspaceName != null && catalog.getWorkspaceByName(workspaceName) == null) { throw new ResourceNotFoundException("Workspace " + workspaceName + " not found"); } LOGGER.info( "DELETE layer group " + layerGroupName ); LayerGroupInfo lg = workspaceName == null ? catalog.getLayerGroupByName( layerGroupName ) : catalog.getLayerGroupByName(workspaceName, layerGroupName); catalog.remove( lg ); } @Override public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { return LayerGroupInfo.class.isAssignableFrom(methodParameter.getParameterType()); } @Override public void configurePersister(XStreamPersister persister, XStreamMessageConverter converter) { persister.setCallback(new XStreamPersister.Callback() { @Override protected Class<LayerGroupInfo> getObjectClass() { return LayerGroupInfo.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 layerGroup = uriTemplateVars.get("layerGroupName"); if (layerGroup == null) { return null; } return catalog.getLayerGroupByName(workspace, layerGroup); } @Override protected void postEncodeReference(Object obj, String ref, String prefix, HierarchicalStreamWriter writer, MarshallingContext context) { if ( obj instanceof StyleInfo ) { StringBuilder link = new StringBuilder(); if (prefix != null) { link.append("/workspaces/").append(converter.encode(prefix)); } link.append("/styles/").append(converter.encode(ref)); converter.encodeLink(link.toString(), writer); } if ( obj instanceof LayerInfo ) { converter.encodeLink("/layers/" + converter.encode(ref), writer); } } }); } }