/* (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.Catalog; import org.geoserver.catalog.CatalogBuilder; import org.geoserver.catalog.CatalogInfo; import org.geoserver.catalog.NamespaceInfo; import org.geoserver.catalog.ResourceInfo; 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.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 + "/namespaces", produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_HTML_VALUE }) public class NamespaceController extends AbstractCatalogController { private static final Logger LOGGER = Logging.getLogger(NamespaceController.class); @Autowired public NamespaceController(@Qualifier("catalog") Catalog catalog) { super(catalog); } @GetMapping(value = "/{namespaceName}", produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.TEXT_HTML_VALUE, MediaType.APPLICATION_XML_VALUE }) public RestWrapper<NamespaceInfo> namespaceGet(@PathVariable String namespaceName) { NamespaceInfo namespace = catalog.getNamespaceByPrefix(namespaceName); if (namespace == null) { throw new ResourceNotFoundException("No such namespace: '" + namespaceName + "' found"); } LOGGER.info("GET " + namespaceName); LOGGER.info("got " + namespace.getName()); return wrapObject(namespace, NamespaceInfo.class); } @GetMapping public RestWrapper getNamespaces() { List<NamespaceInfo> wkspaces = catalog.getNamespaces(); return wrapList(wkspaces, NamespaceInfo.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> namespaceGet(@RequestBody NamespaceInfo namespace, UriComponentsBuilder builder) { catalog.add(namespace); String name = namespace.getName(); LOGGER.info("Added namespace " + name); //JD: we need to keep namespace and workspace in sync, so create a worksapce // if one does not already exists, we can remove this once we get to a point // where namespace is just an attribute on a layer, and not a containing element if ( catalog.getWorkspaceByName( namespace.getPrefix() ) == null ) { WorkspaceInfo ws = catalog.getFactory().createWorkspace(); ws.setName( namespace.getPrefix() ); catalog.add( ws ); } // 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 = "/{prefix}", consumes = { MediaType.TEXT_XML_VALUE, MediaType.APPLICATION_XML_VALUE, MediaTypeExtensions.TEXT_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE }) public void namespacePut( @RequestBody NamespaceInfo namespace, @PathVariable String prefix, UriComponentsBuilder builder) { if ("default".equals(prefix)) { catalog.setDefaultNamespace(namespace); } else { // name must exist NamespaceInfo nsi = catalog.getNamespaceByPrefix(prefix); if (nsi == null) { throw new RestException("Can't change a non existant namespace (" + prefix + ")", HttpStatus.NOT_FOUND); } String infoName = namespace.getName(); if (infoName != null && !prefix.equals(infoName)) { throw new RestException("Can't change name of namespace", HttpStatus.FORBIDDEN); } new CatalogBuilder(catalog).updateNamespace(nsi, namespace); catalog.save(nsi); } } @DeleteMapping(path = "/{prefix}") protected void namespaceDelete(@PathVariable String prefix) { NamespaceInfo ns = catalog.getNamespaceByPrefix(prefix); if (prefix.equals("default")) { throw new RestException("Can't delete the default namespace", HttpStatus.METHOD_NOT_ALLOWED); } if (ns == null) { throw new RestException("Namespace '" + prefix + "' not found", HttpStatus.NOT_FOUND); } if (!catalog.getResourcesByNamespace(ns, ResourceInfo.class).isEmpty()) { throw new RestException("Namespace not empty", HttpStatus.UNAUTHORIZED); } catalog.remove(ns); } private UriComponents getUriComponents(String name, UriComponentsBuilder builder) { UriComponents uriComponents; uriComponents = builder.path("/namespaces/{id}").buildAndExpand(name); return uriComponents; } @Override protected <T> ObjectWrapper createObjectWrapper(Class<T> clazz) { return new ObjectToMapWrapper<NamespaceInfo>(NamespaceInfo.class) { @Override protected void wrapInternal(Map properties, SimpleHash model, NamespaceInfo namespace) { if (properties == null) { try { properties = model.toMap(); } catch (TemplateModelException e) { // TODO Auto-generated catch block e.printStackTrace(); } } NamespaceInfo def = catalog.getDefaultNamespace(); if (def.equals(namespace)) { properties.put("isDefault", Boolean.TRUE); } else { properties.put("isDefault", Boolean.FALSE); } List<Map<String, Map<String, String>>> resources = new ArrayList<>(); List<ResourceInfo> res = catalog.getResourcesByNamespace(namespace, ResourceInfo.class); for (ResourceInfo r : res) { HashMap<String, String> props = new HashMap<>(); props.put("name", r.getName()); props.put("description", r.getDescription()); resources.add(Collections.singletonMap("properties", props)); } properties.put("resources", resources); } @Override protected void wrapInternal(SimpleHash model, @SuppressWarnings("rawtypes") Collection object) { for (Object w : object) { NamespaceInfo ns = (NamespaceInfo) w; wrapInternal(null, model, ns); } } }; } @Override public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { return NamespaceInfo.class.isAssignableFrom(methodParameter.getParameterType()); } @Override public void configurePersister(XStreamPersister persister, XStreamMessageConverter converter) { persister.setCallback(new XStreamPersister.Callback() { @Override protected Class<NamespaceInfo> getObjectClass() { return NamespaceInfo.class; } @Override protected CatalogInfo getCatalogObject() { Map<String, String> uriTemplateVars = (Map<String, String>) RequestContextHolder .getRequestAttributes() .getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST); String prefix = uriTemplateVars.get("namespaceName"); if (prefix == null) { return null; } return catalog.getNamespaceByPrefix(prefix); } @Override protected void postEncodeNamespace(NamespaceInfo cs, HierarchicalStreamWriter writer, MarshallingContext context) { } @Override protected void postEncodeReference(Object obj, String ref, String prefix, HierarchicalStreamWriter writer, MarshallingContext context) { if (obj instanceof NamespaceInfo) { converter.encodeLink("/namespaces/" + converter.encode(ref), writer); } } }); } }