/* (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.backuprestore.rest;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.util.List;
import java.util.logging.Level;
import javax.servlet.http.HttpServletResponse;
import org.geoserver.backuprestore.Backup;
import org.geoserver.backuprestore.BackupExecutionAdapter;
import org.geoserver.config.util.XStreamPersister;
import org.geoserver.rest.RestBaseController;
import org.geoserver.rest.converters.XStreamMessageConverter;
import org.geoserver.rest.util.MediaTypeExtensions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
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.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
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.util.UriComponentsBuilder;
import com.thoughtworks.xstream.XStream;
/**
* REST Backup Controller
*
* <pre>/br/backup[/<backupId>][.zip]</pre>
*
* @author "Alessio Fabiani" <alessio.fabiani@geo-solutions.it>, GeoSolutions
*
*/
@RestController
@ControllerAdvice
@RequestMapping(path = RestBaseController.ROOT_PATH + "/br/",
produces = {
MediaType.APPLICATION_JSON_VALUE,
MediaType.APPLICATION_XML_VALUE,
MediaType.TEXT_HTML_VALUE})
public class BackupController extends AbstractBackupRestoreController {
@Autowired
public BackupController(@Qualifier("backupFacade") Backup backupFacade) {
assert backupFacade != null;
this.backupFacade = backupFacade;
}
@GetMapping(path = "backup{.+}", produces = {
MediaType.APPLICATION_JSON_VALUE,
MediaType.TEXT_XML_VALUE,
MediaType.APPLICATION_XML_VALUE})
public Object backupGet(
@RequestParam(name = "format", required = false) String format) {
Object lookup = lookupBackupExecutionsContext(null, true, false);
if (lookup != null) {
if (lookup instanceof BackupExecutionAdapter) {
return wrapObject((BackupExecutionAdapter)lookup, BackupExecutionAdapter.class);
} else {
return wrapList((List<BackupExecutionAdapter>)lookup, BackupExecutionAdapter.class);
}
}
return null;
}
@GetMapping(path = "backup/{backupId:.+}", produces = {
MediaType.APPLICATION_JSON_VALUE,
MediaType.TEXT_XML_VALUE,
MediaType.APPLICATION_XML_VALUE,
MediaType.ALL_VALUE})
public Object backupGet(
@RequestParam(name = "format", required = false) String format,
@PathVariable String backupId,
HttpServletResponse response) {
Object lookup = lookupBackupExecutionsContext(getExecutionIdFilter(backupId), true, false);
if (lookup != null) {
if (lookup instanceof BackupExecutionAdapter) {
if (backupId.endsWith(".zip")) {
try {
// get your file as InputStream
File file = ((BackupExecutionAdapter)lookup).getArchiveFile().file();
InputStream is = new FileInputStream(file);
// copy it to response's OutputStream
org.apache.commons.io.IOUtils.copy(is, response.getOutputStream());
response.flushBuffer();
} catch (IOException ex) {
LOGGER.log(Level.INFO, "Error writing file to output stream.", ex);
throw new RuntimeException("IOError writing file to output stream");
}
} else {
return wrapObject((BackupExecutionAdapter)lookup, BackupExecutionAdapter.class);
}
} else {
return wrapList((List<BackupExecutionAdapter>)lookup, BackupExecutionAdapter.class);
}
}
return null;
}
@DeleteMapping(path = "backup/{backupId:.+}", produces = {
MediaType.APPLICATION_JSON_VALUE,
MediaType.TEXT_XML_VALUE,
MediaType.APPLICATION_XML_VALUE})
public Object backupDelete(
@RequestParam(name = "format", required = false) String format,
@PathVariable String backupId) throws IOException {
final String executionId = getExecutionIdFilter(backupId);
Object lookup = lookupBackupExecutionsContext(executionId, true, false);
if (lookup != null) {
if (lookup instanceof BackupExecutionAdapter) {
try {
getBackupFacade().abandonExecution(Long.valueOf(executionId));
} catch (Exception e) {
throw new IOException(e);
}
return wrapObject((BackupExecutionAdapter)lookup, BackupExecutionAdapter.class);
} else {
return wrapList((List<BackupExecutionAdapter>)lookup, BackupExecutionAdapter.class);
}
}
return null;
}
@PostMapping(value = {"/backup"}, consumes = {
MediaType.TEXT_XML_VALUE,
MediaType.APPLICATION_XML_VALUE,
MediaType.APPLICATION_JSON_VALUE,
MediaTypeExtensions.TEXT_JSON_VALUE})
@ResponseStatus(HttpStatus.CREATED)
public Object backupPost(
@RequestBody(required = true) BackupExecutionAdapter backup,
@RequestHeader("Content-Type") String contentType,
UriComponentsBuilder builder) throws IOException {
BackupExecutionAdapter execution = null;
if (backup.getId() != null) {
Object lookup = lookupBackupExecutionsContext(String.valueOf(backup.getId()), false, false);
if (lookup != null) {
// Backup instance already exists... trying to restart it.
try {
getBackupFacade().restartExecution(backup.getId());
LOGGER.log(Level.INFO, "Backup restarted: " + backup.getArchiveFile());
return wrapObject((BackupExecutionAdapter)lookup, BackupExecutionAdapter.class);
} catch (Exception e) {
LOGGER.log(Level.WARNING, "Could not restart the backup: " + backup.getArchiveFile());
throw new IOException(e);
}
}
} else {
// Start a new execution asynchronously. You will need to query for the status in order to follow the progress.
execution = getBackupFacade().runBackupAsync(
backup.getArchiveFile(),
backup.isOverwrite(),
backup.getFilter(),
asParams(backup.getOptions()));
LOGGER.log(Level.INFO, "Backup file generated: " + backup.getArchiveFile());
return wrapObject((BackupExecutionAdapter)execution, BackupExecutionAdapter.class);
}
return null;
}
/**
* From {@link RestBaseController}
*
* ... * Any extending classes which override {@link #configurePersister(XStreamPersister, XStreamMessageConverter)}, and
* require this configuration for reading objects from incoming requests must also be annotated with
* {@link org.springframework.web.bind.annotation.ControllerAdvice} and override the {@link #supports(MethodParameter, Type, Class)}
* method...
*/
@Override
public boolean supports(MethodParameter methodParameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
return BackupExecutionAdapter.class.isAssignableFrom(methodParameter.getParameterType());
}
@Override
public void configurePersister(XStreamPersister persister, XStreamMessageConverter converter) {
XStream xstream = persister.getXStream();
intializeXStreamContext(xstream);
}
}