/* (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.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.geoserver.catalog.Catalog;
import org.geoserver.platform.GeoServerResourceLoader;
import org.geoserver.platform.resource.Paths;
import org.geoserver.platform.resource.Resource;
import org.geoserver.platform.resource.Resource.Type;
import org.geoserver.platform.resource.Resources;
import org.geoserver.rest.ResourceNotFoundException;
import org.geoserver.rest.RestBaseController;
import org.geoserver.rest.RestException;
import org.geoserver.rest.util.MediaTypeExtensions;
import org.geoserver.rest.util.RESTUtils;
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.http.HttpStatus;
import org.springframework.http.MediaType;
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.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
/**
* Controller responsible for freemarker templates.
*
* <ul>
* <li>templates</li>
* <li>templates/{templateName}.ftl</li>
* <li>workspaces/{workspaceName}/templates</li>
* <li>workspaces/{workspaceName}/templates/{template>.ftl</li>
* <li>workspaces/{workspaceName}/datastores/{storeName}/templates</li>
* <li>workspaces/{workspaceName}/datastores/{storeName}/templates/{templateName}.ftl</li>
* <li>workspaces/{workspaceName}/datastores/{storeName}/featuretypes/{featureTypeName}/templates</li>
* <li>workspaces/{workspaceName}/datastores/{storeName}/teaturetypes/{featureTypeName}/templates/{templateName}.ftl</li>
* <li>workspaces/{workspaceName}/coveragestores/{storeName}/coverage/{featureTypeName}/templates</li>
* <li>workspaces/{workspaceName}/coveragestores/{storeName}/coverage/{featureTypeName}/templates/{templateName}.ftl</li>
* </ul>
* @author Jody Garnett (Boundless)
*/
@RestController
@ControllerAdvice
@RequestMapping(path = {
RestBaseController.ROOT_PATH + "/templates",
RestBaseController.ROOT_PATH + "/workspaces/{workspaceName}/templates",
RestBaseController.ROOT_PATH + "/workspaces/{workspaceName}/datastores/{storeName}/templates",
RestBaseController.ROOT_PATH + "/workspaces/{workspaceName}/datastores/{storeName}/featuretypes/{featureTypeName}/templates",
RestBaseController.ROOT_PATH + "/workspaces/{workspaceName}/coveragestores/{storeName}/templates",
RestBaseController.ROOT_PATH + "/workspaces/{workspaceName}/coveragestores/{storeName}/coverages/{featureTypeName}/templates"})
public class TemplateController extends AbstractCatalogController {
private GeoServerResourceLoader resources;
static Logger LOGGER = Logging.getLogger("org.geoserver.catalog.rest");
@Autowired
public TemplateController(@Qualifier("catalog") Catalog catalog) {
super(catalog);
resources = catalog.getResourceLoader();
}
/**
* Template definition.
*
* @return Template definition
*/
@DeleteMapping(value = "/{templateName}")
public void templateDelete(
HttpServletResponse response,
@PathVariable(required = false) String workspaceName,
@PathVariable(required = false) String storeName,
@PathVariable(required = false) String featureTypeName,
@PathVariable String templateName) {
String filename = templateName+"."+ MediaTypeExtensions.FTL_EXTENSION;
String path = Paths.path(path(workspaceName, storeName, featureTypeName ), filename);
Resource resource = resources.get(path);
if( resource.getType() != Type.RESOURCE ){
throw new ResourceNotFoundException("Template not found: '"+path+"'");
}
boolean removed = resource.delete();
if (!removed) {
throw new RestException("Template '" + path + "' not removed", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
/**
* Template definition.
*
* @return Template Definitin
*/
@GetMapping(value = "/{templateName}", produces = {
MediaTypeExtensions.TEXT_FTL_VALUE})
public void templateGet(
@PathVariable(required = false) String workspaceName,
@PathVariable(required = false) String storeName,
@PathVariable(required = false) String featureTypeName,
@PathVariable String templateName,
HttpServletResponse response) {
String filename = templateName+"."+ MediaTypeExtensions.FTL_EXTENSION;
String path = Paths.path(path(workspaceName, storeName, featureTypeName ), filename);
Resource resource = resources.get(path);
if( resource.getType() != Type.RESOURCE ){
throw new ResourceNotFoundException("Template not found: '"+path+"'");
}
byte[] bytes;
try {
bytes = resource.getContents();
response.setContentType(MediaTypeExtensions.TEXT_FTL_VALUE);
response.setContentLength(bytes.length);
try( ServletOutputStream output = response.getOutputStream() ){
output.write(bytes);
output.flush();
}
} catch (IOException problem) {
throw new RestException(problem.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR,problem);
}
}
/**
* All templates as JSON, XML or HTML.
*
* @return All templates
*/
@PutMapping(value = "/{templateName}", consumes = {
MediaTypeExtensions.TEXT_FTL_VALUE,
MediaType.TEXT_PLAIN_VALUE})
@ResponseStatus(HttpStatus.CREATED)
public void templatePut(
@PathVariable(required = false) String workspaceName,
@PathVariable(required = false) String storeName,
@PathVariable(required = false) String featureTypeName,
@PathVariable String templateName,
HttpServletRequest request) {
String filename = templateName + "." + MediaTypeExtensions.FTL_EXTENSION;
String path = path(workspaceName, storeName, featureTypeName);
Resource directory = resources.get(path);
Resource resource = fileUpload(directory, filename, request);
if (LOGGER.isLoggable(Level.INFO)) {
LOGGER.info("PUT template: " + resource.path());
}
}
//
// List Templates
//
// These endpoints return a list of FreeMarkerTemplateInfo, that is converted to the appropriate output.
//
/**
* All templates as JSON, XML or HTML.
*
* @return All templates
*/
@GetMapping(produces = {
MediaType.TEXT_HTML_VALUE, // this is the default
MediaType.APPLICATION_JSON_VALUE,
MediaType.APPLICATION_XML_VALUE})
public RestWrapper<TemplateInfo> templatesGet(
@PathVariable(required = false) String workspaceName,
@PathVariable(required = false) String storeName,
@PathVariable(required = false) String featureTypeName){
String path = path(workspaceName, storeName, featureTypeName );
Resource directory = resources.get(path);
switch( directory.getType() ){
case RESOURCE:
case UNDEFINED:
throw new ResourceNotFoundException("Directory not found: '"+path+"'");
default:
List<Resource> files = Resources.list(directory, new Resources.ExtensionFilter("FTL"), false);
List<TemplateInfo> list = new ArrayList<>();
for (Resource file : files) {
list.add(new TemplateInfo(file));
}
return wrapList(list, TemplateInfo.class);
}
}
/**
* Verifies mime type
* @param directory
* @param filename
* @param request
* @return
*/
private Resource fileUpload(Resource directory, String filename, HttpServletRequest request) {
if (LOGGER.isLoggable(Level.INFO)) {
LOGGER.info("PUT file: mimetype=" + request.getContentType() + ", path="
+ directory.path());
}
try {
return RESTUtils.handleBinUpload(filename, directory, false, request);
} catch (IOException problem) {
throw new RestException(problem.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR,
problem);
}
}
/**
* Construct "get directory path"
* @param workspace Workspace, optional
* @param store DataStore or Coverage store, requires workspace
* @param type FeatureType or Coverage, requires store
* @return template path
*/
public static String path(String workspace, String store, String type) {
List<String> path = new ArrayList<>();
path.add("workspaces");
if (workspace != null) {
path.add(workspace);
if (store != null) {
path.add(store);
if (type != null) {
path.add(type);
}
}
}
return Paths.path( path.toArray(new String[] {}) );
}
}