/* (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.util;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.io.FilenameUtils;
import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.CoverageStoreInfo;
import org.geoserver.catalog.DataStoreInfo;
import org.geoserver.catalog.MetadataMap;
import org.geoserver.catalog.StoreInfo;
import org.geoserver.catalog.WorkspaceInfo;
import org.geoserver.config.GeoServer;
import org.geoserver.config.GeoServerInfo;
import org.geoserver.config.SettingsInfo;
import org.geoserver.platform.GeoServerExtensions;
import org.geoserver.platform.resource.Files;
import org.geoserver.platform.resource.Resource;
import org.geoserver.platform.resource.Resources;
import org.geoserver.rest.RestException;
import org.geotools.util.logging.Logging;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
/**
* Utility class for Restlets.
*
* @author David Winslow, OpenGeo
* @author Simone Giannecchini, GeoSolutions
* @author Justin Deoliveira, OpenGeo
*
*/
public class RESTUtils {
static Logger LOGGER = Logging.getLogger("org.geoserver.rest.util");
public static final String ROOT_KEY = "root";
public static final String QUIET_ON_NOT_FOUND_KEY = "quietOnNotFound";
/**
* Reads content from the body of a request and writes it to a file.
*
* @param fileName The name of the file to write out.
* @param directory The directory to write the file to.
* @param deleteDirectoryContent Delete directory content if the file already exists.
* @param request The request.
*
* @return The file object representing the newly written file.
*
* @throws IOException Any I/O errors that occur.
*
* TODO: move this to IOUtils.
*/
public static org.geoserver.platform.resource.Resource handleBinUpload(String fileName,
org.geoserver.platform.resource.Resource directory, boolean deleteDirectoryContent, HttpServletRequest request) throws IOException {
return handleBinUpload(fileName, directory, deleteDirectoryContent, request, null);
}
/**
* Reads content from the body of a request and writes it to a file.
*
* @param fileName The name of the file to write out.
* @param directory The directory to write the file to.
* @param deleteDirectoryContent Delete directory content if the file already exists.
* @param request The request.
*
* @return The file object representing the newly written file.
*
* @throws IOException Any I/O errors that occur.
*
* TODO: move this to IOUtils.
*/
public static org.geoserver.platform.resource.Resource handleBinUpload(String fileName, org.geoserver.platform.resource.Resource directory, boolean deleteDirectoryContent,
HttpServletRequest request, String workSpace) throws IOException {
// Creation of a StringBuilder for the selected file
StringBuilder itemPath = new StringBuilder(fileName);
// Mediatype associated to the input file
MediaType mediaType = request.getContentType() == null ? null: MediaType.valueOf(request.getContentType());
// Only zip files are not remapped
if(mediaType == null || !isZipMediaType( mediaType )){
String baseName = FilenameUtils.getBaseName(fileName);
String itemName = FilenameUtils.getName(fileName);
// Store parameters used for mapping the file path
Map<String, String> storeParams = new HashMap<>();
// Mapping item path
remapping(workSpace, baseName, itemPath, itemName, storeParams);
}
final org.geoserver.platform.resource.Resource newFile = directory.get(itemPath.toString());
if(Resources.exists(newFile)) {
if (deleteDirectoryContent) {
for (Resource file : directory.list()) {
file.delete();
}
} else {
// delete the file, otherwise replacing it with a smaller one will leave bytes at the end
newFile.delete();
}
}
try(OutputStream os = newFile.out()) {
IOUtils.copy(request.getInputStream(), os);
}
return newFile;
}
/**
* Reads a url from the body of a request, reads the contents of the url and writes it to a file.
*
* @param fileName The name of the file to write.
* @param directory The directory to write the new file to.
* @param request The request.
*
* @return The file object representing the newly written file.
*
* @throws IOException Any I/O errors that occur.
*
* TODO: move this to IOUtils
*/
public static org.geoserver.platform.resource.Resource handleURLUpload(
String fileName, String workSpace, org.geoserver.platform.resource.Resource directory, HttpServletRequest request)
throws IOException {
//Initial remapping of the input file
StringBuilder itemPath = new StringBuilder(fileName);
// Mediatype associated to the input file
MediaType mediaType = request.getContentType() != null ? MediaType.valueOf(request.getContentType()) : null;
// Only zip files are not remapped
if(mediaType == null || !isZipMediaType( mediaType )){
String baseName = FilenameUtils.getBaseName(fileName);
// Store parameters used for mapping the file path
Map<String, String> storeParams = new HashMap<>();
String itemName = FilenameUtils.getName(fileName);
// Mapping item path
remapping(workSpace, baseName, itemPath, itemName, storeParams);
}
//this may exists already, but we don't fail here since
//it might be old and unused, if needed we fail later while copying
org.geoserver.platform.resource.Resource newFile = directory.get(itemPath.toString());
//get the URL for this file to upload
final InputStream inStream = request.getInputStream();
final String stringURL = IOUtils.getStringFromStream(inStream);
final URL fileURL = new URL(stringURL);
////
//
// Now do the real upload
//
////
final InputStream inputStream = fileURL.openStream();
final OutputStream outStream = newFile.out();
IOUtils.copyStream(inputStream, outStream, true, true);
return newFile;
}
/**
* Handles an upload using the EXTERNAL method.
*
* @param request
* @throws IOException
*/
public static org.geoserver.platform.resource.Resource handleEXTERNALUpload(HttpServletRequest request) throws IOException {
//get the URL for this file to upload
InputStream inStream = null;
URL fileURL ;
try {
inStream = request.getInputStream();
final String stringURL = IOUtils.getStringFromStream(inStream);
fileURL = new URL(stringURL);
} finally {
IOUtils.closeQuietly(inStream);
}
final File inputFile = IOUtils.URLToFile(fileURL);
if(inputFile == null || !inputFile.exists()) {
throw new RestException("Failed to locate the input file " + fileURL, HttpStatus.BAD_REQUEST);
} else if(!inputFile.canRead()) {
throw new RestException("Input file is not readable, check filesystem permissions: " + fileURL,
HttpStatus.BAD_REQUEST);
}
return Files.asResource(inputFile);
}
static Set<String> ZIP_MIME_TYPES = new HashSet();
static {
ZIP_MIME_TYPES.add( "application/zip" );
ZIP_MIME_TYPES.add( "multipart/x-zip" );
ZIP_MIME_TYPES.add( "application/x-zip-compressed" );
}
/**
* Determines if the specified request contains a zip stream.
*/
public static boolean isZipMediaType( HttpServletRequest request) {
return ZIP_MIME_TYPES.contains( request.getContentType() );
}
/**
* Determines if the specified media type represents a zip stream.
*/
public static boolean isZipMediaType( MediaType mediaType ) {
return ZIP_MIME_TYPES.contains( mediaType.toString() );
}
/**
* Unzips a zip a file to a specified directory, deleting the zip file after unpacking.
*
* @param zipFile The zip file.
* @param outputDirectory The directory to unpack the contents to.
*
* @throws IOException Any I/O errors that occur.
*
* TODO: move this to IOUtils
*/
public static void unzipFile( org.geoserver.platform.resource.Resource zipFile,
org.geoserver.platform.resource.Resource outputDirectory ) throws IOException {
unzipFile(zipFile, outputDirectory, null, null, null, null, false);
}
/**
* Unzips a zip a file to a specified directory, deleting the zip file after unpacking.
*
* @param zipFile The zip file.
* @param outputDirectory The directory to unpack the contents to.
* @param external
*
* @throws IOException Any I/O errors that occur.
*
* TODO: move this to IOUtils
*/
public static void unzipFile(org.geoserver.platform.resource.Resource zipFile,
org.geoserver.platform.resource.Resource outputDirectory, String workspace,
String store, HttpServletRequest request, List<org.geoserver.platform.resource.Resource> files,
boolean external) throws IOException {
if (outputDirectory == null) {
outputDirectory = zipFile.parent();
}
ZipFile archive = new ZipFile(zipFile.file());
IOUtils.inflate(archive, outputDirectory, null, workspace, store, files, external);
zipFile.delete();
}
/**
* Fetch a request attribute as a String, accounting for URL-encoding.
*
* @param request the Restlet Request object that might contain the attribute
* @param name the name of the attribute to retrieve
*
* @return the attribute, URL-decoded, if it exists and is a valid URL-encoded string, or null
* otherwise
*/
public static String getAttribute(HttpServletRequest request, String name) {
Object o = request.getAttribute(name);
return decode(o);
}
public static String getQueryStringValue(HttpServletRequest request, String key) {
String value = request.getParameter(key);
return decode(value);
}
static String decode(Object value) {
if (value == null) {
return null;
}
try {
return URLDecoder.decode(value.toString(), "UTF-8");
} catch (UnsupportedEncodingException e) {
return null;
}
}
/**
* Method for searching an item inside the MetadataMap.
*
* @param workspaceName
* @param storeName
* @param catalog
*
*/
public static String getItem(String workspaceName, String storeName, Catalog catalog, String key) {
// Initialization of a null String containing the root directory to use for the input store config
String item;
// ////////////////////////////////////
//
// Check Store info if present
//
// ////////////////////////////////////
item = extractMapItem(loadMapfromStore(storeName, catalog), key);
// ////////////////////////////////////
//
// Check WorkSpace info if not found
// inside the Store Info
//
// ////////////////////////////////////
if (item == null) {
item = extractMapItem(loadMapfromWorkSpace(workspaceName, catalog), key);
}
// ////////////////////////////////////
//
// Finally check Global info
//
// ////////////////////////////////////
if (item == null) {
item = extractMapItem(loadMapFromGlobal(), key);
}
return item;
}
/**
* This method is used for extracting the metadata map from the selected store
*
* @param storeName
* @param catalog
*
*/
public static MetadataMap loadMapfromStore(String storeName, Catalog catalog) {
StoreInfo storeInfo = catalog.getStoreByName(storeName, CoverageStoreInfo.class);
if(storeInfo == null){
storeInfo = catalog.getStoreByName(storeName, DataStoreInfo.class);
}
// If the Store is present, then the associated MetadataMap is selected
if(storeInfo != null){
return storeInfo.getMetadata();
}
return null;
}
/**
* This method is used for extracting the metadata map from the selected workspace
*
* @param workspaceName
* @param catalog
*
*/
public static MetadataMap loadMapfromWorkSpace(String workspaceName, Catalog catalog) {
WorkspaceInfo wsInfo = catalog.getWorkspaceByName(workspaceName);
// If the WorkSpace is present, then the associated MetadataMap is selected
if(wsInfo != null){
GeoServer gs = GeoServerExtensions.bean(GeoServer.class);
SettingsInfo info = gs.getSettings(wsInfo);
return info != null ? info.getMetadata() : null;
}
return null;
}
/**
* This method is used for extracting the metadata map from the global settings
*
*
*/
public static MetadataMap loadMapFromGlobal() {
GeoServerInfo gsInfo = GeoServerExtensions.bean(GeoServer.class).getGlobal();
// Global info should be always not null
if(gsInfo != null){
SettingsInfo info = gsInfo.getSettings();
return info != null ? info.getMetadata() : null;
}
return null;
}
/**
* Extraction of the item from the metadata map
*
* @param map
* @param key
*
*/
public static String extractMapItem(MetadataMap map, String key) {
if(map != null && !map.isEmpty()){
String item = map.get(key, String.class);
if (item != null && !item.isEmpty()){
return item;
}
}
return null;
}
public static String getRootDirectory(String workspaceName, String storeName, Catalog catalog) {
String rootDir = getItem(workspaceName, storeName, catalog, ROOT_KEY);
if(rootDir != null){
// Check if it already exists
File rootFile = new File(rootDir);
if (rootFile.isAbsolute()) {
if (!rootFile.exists()) {
if (!rootFile.mkdirs()) {
rootFile.delete();
return null;
}
} else {
if (!rootFile.isDirectory()) {
LOGGER.info(rootDir + " ROOT path is not a directory");
return null;
}
}
}
}
return rootDir;
}
public static void remapping(String workspace, String store, StringBuilder itemPath,
String initialFileName, Map<String, String> storeParams) throws IOException {
// Selection of the available PathMapper
List<RESTUploadPathMapper> mappers = GeoServerExtensions
.extensions(RESTUploadPathMapper.class);
// Mapping the item path
for (RESTUploadPathMapper mapper : mappers) {
mapper.mapItemPath(workspace, store, storeParams, itemPath, initialFileName);
}
}
/**
* Unzips a InputStream to a directory
*
* @param in
* @param outputDirectory
* @throws IOException
*/
public static void unzipInputStream(InputStream in, File outputDirectory) throws IOException {
ZipInputStream zin = null;
try {
zin = new ZipInputStream(in);
ZipEntry entry;
byte[] buffer = new byte[2048];
while((entry = zin.getNextEntry())!=null) {
String outpath = outputDirectory.getAbsolutePath() + "/" + entry.getName();
FileOutputStream output = null;
try {
output = new FileOutputStream(outpath);
int len;
while ((len = zin.read(buffer)) > 0)
{
output.write(buffer, 0, len);
}
} finally {
IOUtils.closeQuietly(output);
}
}
} finally {
IOUtils.closeQuietly(zin);
}
}
}