/* (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.lang.reflect.Type; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Logger; import freemarker.template.ObjectWrapper; 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.DataStoreInfo; import org.geoserver.catalog.NamespaceInfo; import org.geoserver.catalog.StoreInfo; import org.geoserver.catalog.WMSStoreInfo; import org.geoserver.catalog.WorkspaceInfo; import org.geoserver.config.util.XStreamPersister; import org.geoserver.rest.ObjectToMapWrapper; 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.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.ResponseStatus; 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; import freemarker.template.SimpleHash; import freemarker.template.TemplateModelException; @RestController @RequestMapping(path = RestBaseController.ROOT_PATH + "/workspaces", produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_HTML_VALUE }) public class WorkspaceController extends AbstractCatalogController { private static final Logger LOGGER = Logging.getLogger(WorkspaceController.class); @Autowired public WorkspaceController(@Qualifier("catalog") Catalog catalog) { super(catalog); } @GetMapping public RestWrapper workspacesGet() { List<WorkspaceInfo> wkspaces = catalog.getWorkspaces(); return wrapList(wkspaces, WorkspaceInfo.class); } @GetMapping(value = "/{workspaceName}") public RestWrapper<WorkspaceInfo> getWorkspace(@PathVariable String workspaceName) { WorkspaceInfo wkspace = catalog.getWorkspaceByName(workspaceName); if (wkspace == null) { throw new ResourceNotFoundException("No such workspace: '" + workspaceName + "' found"); } LOGGER.info("GET " + workspaceName); LOGGER.info("got " + wkspace.getName()); return wrapObject(wkspace, WorkspaceInfo.class); } @PostMapping(consumes = { MediaType.TEXT_XML_VALUE, MediaType.APPLICATION_XML_VALUE, MediaTypeExtensions.TEXT_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE }) @ResponseStatus(HttpStatus.CREATED) public ResponseEntity<String> workspacePost( @RequestBody WorkspaceInfo workspace, @RequestParam(defaultValue = "false", name = "default") boolean makeDefault, UriComponentsBuilder builder) { if(catalog.getWorkspaceByName(workspace.getName())!=null) { throw new RestException("Workspace '"+workspace.getName()+"' already exists", HttpStatus.UNAUTHORIZED); } catalog.add(workspace); String name = workspace.getName(); LOGGER.info("Added workspace " + name); if (makeDefault) { catalog.setDefaultWorkspace(workspace); LOGGER.info("made workspace " + name + " default"); } LOGGER.info("POST Style " + name); //create a namespace corresponding to the workspace if one does not // already exist NamespaceInfo namespace = catalog.getNamespaceByPrefix( workspace.getName() ); if ( namespace == null ) { LOGGER.fine( "Automatically creating namespace for workspace " + workspace.getName() ); namespace = catalog.getFactory().createNamespace(); namespace.setPrefix( workspace.getName() ); namespace.setURI( "http://" + workspace.getName() ); catalog.add( namespace ); } // build the new path UriComponents uriComponents = getUriComponents(name, builder); HttpHeaders headers = new HttpHeaders(); headers.setLocation(uriComponents.toUri()); return new ResponseEntity<>(name, headers, HttpStatus.CREATED); } @PutMapping(value = "/{workspaceName}", consumes = { MediaType.TEXT_XML_VALUE, MediaType.APPLICATION_XML_VALUE, MediaTypeExtensions.TEXT_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE }) public void workspacePut( @RequestBody WorkspaceInfo workspace, @PathVariable String workspaceName, UriComponentsBuilder builder) { if ( "default".equals( workspaceName ) ) { catalog.setDefaultWorkspace( workspace ); } else { // name must exist WorkspaceInfo wks = catalog.getWorkspaceByName(workspaceName); if (wks == null) { throw new RestException("Can't change a non existant workspace (" + workspaceName + ")", HttpStatus.NOT_FOUND); } String infoName = workspace.getName(); if (infoName != null && !workspaceName.equals(infoName)) { throw new RestException("Can't change name of workspace", HttpStatus.FORBIDDEN); } new CatalogBuilder(catalog).updateWorkspace(wks, workspace); catalog.save(wks); } } @DeleteMapping(path = "/{workspaceName}") protected void workspaceDelete( @PathVariable String workspaceName, @RequestParam(defaultValue = "false", name = "recurse") boolean recurse) { WorkspaceInfo ws = catalog.getWorkspaceByName(workspaceName); if(ws == null) { throw new RestException("Workspace '"+workspaceName+"' not found", HttpStatus.NOT_FOUND); } if (!recurse) { if (!catalog.getStoresByWorkspace(ws, StoreInfo.class).isEmpty()) { throw new RestException("Workspace not empty", HttpStatus.FORBIDDEN); } // check for "linked" workspace NamespaceInfo ns = catalog.getNamespaceByPrefix(ws.getName()); if (ns != null) { if (!catalog.getFeatureTypesByNamespace(ns).isEmpty()) { throw new RestException("Namespace for workspace not empty.", HttpStatus.FORBIDDEN); } catalog.remove(ns); } catalog.remove(ws); } else { // recursive delete new CascadeDeleteVisitor(catalog).visit(ws); } LOGGER.info("DELETE workspace " + ws); } private UriComponents getUriComponents(String name, UriComponentsBuilder builder) { UriComponents uriComponents; uriComponents = builder.path("/workspaces/{id}").buildAndExpand(name); return uriComponents; } @Override protected <T> ObjectWrapper createObjectWrapper(Class<T> clazz) { return new ObjectToMapWrapper<WorkspaceInfo>(WorkspaceInfo.class) { @Override protected void wrapInternal(Map<String, Object> properties, SimpleHash model, WorkspaceInfo wkspace) { if (properties == null) { try { properties = model.toMap(); } catch (TemplateModelException e) { // TODO Auto-generated catch block e.printStackTrace(); } } List<Map<String, Map<String, String>>> dsProps = new ArrayList<>(); List<DataStoreInfo> datasources = catalog.getDataStoresByWorkspace(wkspace); for (DataStoreInfo ds : datasources) { Map<String, String> names = new HashMap<>(); names.put("name", ds.getName()); dsProps.add(Collections.singletonMap("properties", names)); } if (!dsProps.isEmpty()) properties.putIfAbsent("dataStores", dsProps); dsProps = new ArrayList<>(); List<CoverageStoreInfo> coverages = catalog.getCoverageStoresByWorkspace(wkspace); for (CoverageStoreInfo ds : coverages) { Map<String, String> names = new HashMap<>(); names.put("name", ds.getName()); dsProps.add(Collections.singletonMap("properties", names)); } if (!dsProps.isEmpty()) properties.putIfAbsent("coverageStores", dsProps); dsProps = new ArrayList<>(); List<WMSStoreInfo> wmssources = catalog.getStoresByWorkspace(wkspace, WMSStoreInfo.class); for (WMSStoreInfo ds : wmssources) { Map<String, String> names = new HashMap<>(); names.put("name", ds.getName()); dsProps.add(Collections.singletonMap("properties", names)); } if (!dsProps.isEmpty()) properties.putIfAbsent("wmsStores", dsProps); WorkspaceInfo def = catalog.getDefaultWorkspace(); if (def.equals(wkspace)) { properties.put("isDefault", Boolean.TRUE); } else { properties.put("isDefault", Boolean.FALSE); } } @Override protected void wrapInternal(SimpleHash model, @SuppressWarnings("rawtypes") Collection object) { for (Object w : object) { WorkspaceInfo wk = (WorkspaceInfo) w; wrapInternal(null, model, wk); } } }; } @Override public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { return WorkspaceInfo.class.isAssignableFrom(methodParameter.getParameterType()); } @Override public void configurePersister(XStreamPersister persister, XStreamMessageConverter converter) { persister.setCallback(new XStreamPersister.Callback() { @Override protected Class<WorkspaceInfo> getObjectClass() { return WorkspaceInfo.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"); if (workspace == null) { return null; } return catalog.getWorkspaceByName(workspace); } @Override protected void postEncodeWorkspace(WorkspaceInfo cs, HierarchicalStreamWriter writer, MarshallingContext context) { // add a link to the datastores writer.startNode("dataStores"); converter.encodeCollectionLink("datastores", writer); writer.endNode(); writer.startNode("coverageStores"); converter.encodeCollectionLink("coveragestores", writer); writer.endNode(); writer.startNode("wmsStores"); converter.encodeCollectionLink("wmsstores", 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); } } }); } @Override protected String getTemplateName(Object object) { if (object instanceof WorkspaceInfo) { return "WorkspaceInfo"; } return null; } }