package org.eclipse.packagedrone.repo.channel.web.channel;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.MessageFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.servlet.annotation.HttpConstraint;
import javax.servlet.annotation.ServletSecurity.EmptyRoleSemantic;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import org.eclipse.packagedrone.repo.channel.ChannelId;
import org.eclipse.packagedrone.repo.channel.ChannelInformation;
import org.eclipse.packagedrone.repo.channel.ChannelService;
import org.eclipse.packagedrone.repo.channel.transfer.TransferService;
import org.eclipse.packagedrone.repo.channel.web.Tags;
import org.eclipse.packagedrone.sec.web.controller.HttpContraintControllerInterceptor;
import org.eclipse.packagedrone.sec.web.controller.Secured;
import org.eclipse.packagedrone.sec.web.controller.SecuredControllerInterceptor;
import org.eclipse.packagedrone.utils.io.IOConsumer;
import org.eclipse.packagedrone.web.Controller;
import org.eclipse.packagedrone.web.LinkTarget;
import org.eclipse.packagedrone.web.ModelAndView;
import org.eclipse.packagedrone.web.RequestMapping;
import org.eclipse.packagedrone.web.RequestMethod;
import org.eclipse.packagedrone.web.ViewResolver;
import org.eclipse.packagedrone.web.common.CommonController;
import org.eclipse.packagedrone.web.common.InterfaceExtender;
import org.eclipse.packagedrone.web.common.Modifier;
import org.eclipse.packagedrone.web.common.menu.MenuEntry;
import org.eclipse.packagedrone.web.controller.ControllerInterceptor;
import org.eclipse.packagedrone.web.controller.ProfilerControllerInterceptor;
import org.eclipse.packagedrone.web.controller.binding.PathVariable;
import org.eclipse.packagedrone.web.controller.binding.RequestParameter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.io.ByteStreams;
import com.google.common.net.UrlEscapers;
@Secured
@Controller
@ViewResolver ( "/WEB-INF/views/%s.jsp" )
@ControllerInterceptor ( SecuredControllerInterceptor.class )
@HttpConstraint ( rolesAllowed = "MANAGER" )
@ControllerInterceptor ( HttpContraintControllerInterceptor.class )
@ControllerInterceptor ( ProfilerControllerInterceptor.class )
public class TransferController implements InterfaceExtender
{
private final static Logger logger = LoggerFactory.getLogger ( TransferController.class );
private static final MessageFormat EXPORT_PATTERN = new MessageFormat ( "export-channel-{0}-{1,date,yyyyMMdd-HHmm}.zip" );
private static final MessageFormat EXPORT_ALL_PATTERN = new MessageFormat ( "export-all-{0,date,yyyyMMdd-HHmm}.zip" );
private TransferService transferService;
private ChannelService channelService;
public void setTransferService ( final TransferService transferService )
{
this.transferService = transferService;
}
public void setChannelService ( final ChannelService channelService )
{
this.channelService = channelService;
}
protected ModelAndView performExport ( final HttpServletResponse response, final String filename, final IOConsumer<OutputStream> exporter )
{
try
{
final Path tmp = Files.createTempFile ( "export-", null );
try
{
try ( OutputStream tmpStream = new BufferedOutputStream ( new FileOutputStream ( tmp.toFile () ) ) )
{
// first we spool this out to temp file, so that we don't block the channel for too long
exporter.accept ( tmpStream );
}
response.setContentLengthLong ( tmp.toFile ().length () );
response.setContentType ( "application/zip" );
response.setHeader ( "Content-Disposition", String.format ( "attachment; filename=%s", filename ) );
try ( InputStream inStream = new BufferedInputStream ( new FileInputStream ( tmp.toFile () ) ) )
{
ByteStreams.copy ( inStream, response.getOutputStream () );
}
return null;
}
finally
{
Files.deleteIfExists ( tmp );
}
}
catch ( final IOException e )
{
return CommonController.createError ( "Failed to export", null, e );
}
}
@RequestMapping ( value = "/channel/{channelId}/export", method = RequestMethod.GET )
@HttpConstraint ( value = EmptyRoleSemantic.PERMIT )
public ModelAndView exportChannel ( @PathVariable ( "channelId" ) final String channelId, final HttpServletResponse response)
{
return performExport ( response, makeExportFileName ( channelId ), ( stream ) -> this.transferService.exportChannel ( channelId, stream ) );
}
@RequestMapping ( value = "/channel/export", method = RequestMethod.GET )
@HttpConstraint ( value = EmptyRoleSemantic.PERMIT )
public ModelAndView exportAll ( final HttpServletResponse response )
{
return performExport ( response, makeExportFileName ( null ), this.transferService::exportAll );
}
@RequestMapping ( value = "/channel/import", method = RequestMethod.GET )
public ModelAndView importChannel ()
{
return new ModelAndView ( "channel/importChannel" );
}
@RequestMapping ( value = "/channel/import", method = RequestMethod.POST )
public ModelAndView importChannelPost ( @RequestParameter ( "file" ) final Part part, @RequestParameter (
value = "useName", required = false ) final boolean useName)
{
try
{
final ChannelId channelId = this.transferService.importChannel ( part.getInputStream (), useName );
return new ModelAndView ( "redirect:/channel/" + UrlEscapers.urlPathSegmentEscaper ().escape ( channelId.getId () ) + "/view" );
}
catch ( final Exception e )
{
logger.warn ( "Failed to import", e );
return CommonController.createError ( "Import", "Channel", "Failed to import channel", e, null );
}
}
@RequestMapping ( value = "/channel/importAll", method = RequestMethod.GET )
public ModelAndView importAll ( final HttpServletResponse response )
{
return new ModelAndView ( "channel/importAll" );
}
@RequestMapping ( value = "/channel/importAll", method = RequestMethod.POST )
public ModelAndView importAllPost ( @RequestParameter ( value = "useNames",
required = false ) final boolean useNames, @RequestParameter ( value = "wipe",
required = false ) final boolean wipe, @RequestParameter ( "file" ) final Part part, @RequestParameter (
value = "location", required = false ) final String location)
{
try
{
if ( location != null && !location.isEmpty () )
{
try ( BufferedInputStream stream = new BufferedInputStream ( new FileInputStream ( new File ( location ) ) ) )
{
this.transferService.importAll ( stream, useNames, wipe );
}
}
else
{
this.transferService.importAll ( part.getInputStream (), useNames, wipe );
}
return new ModelAndView ( "redirect:/channel" );
}
catch ( final Exception e )
{
logger.warn ( "Failed to import", e );
return CommonController.createError ( "Import", "Channel", "Failed to import channel", e, null );
}
}
private String makeExportFileName ( final String channelId )
{
if ( channelId != null )
{
return EXPORT_PATTERN.format ( new Object[] { channelId, new Date () } );
}
else
{
return EXPORT_ALL_PATTERN.format ( new Object[] { new Date () } );
}
}
@Override
public List<MenuEntry> getActions ( final HttpServletRequest request, final Object object )
{
if ( object instanceof ChannelInformation )
{
final ChannelInformation channel = (ChannelInformation)object;
final Map<String, Object> model = new HashMap<> ( 1 );
model.put ( "channelId", channel.getId () );
final List<MenuEntry> result = new LinkedList<> ();
if ( request.getRemoteUser () != null )
{
result.add ( new MenuEntry ( "Maintenance", 160, "Export channel", 200, LinkTarget.createFromController ( TransferController.class, "exportChannel" ).expand ( model ), Modifier.DEFAULT, "export" ) );
}
return result;
}
else if ( Tags.ACTION_TAG_CHANNELS.equals ( object ) )
{
final List<MenuEntry> result = new LinkedList<> ();
if ( request.isUserInRole ( "MANAGER" ) )
{
result.add ( new MenuEntry ( "Maintenance", 160, "Import channel", 200, LinkTarget.createFromController ( TransferController.class, "importChannel" ), Modifier.DEFAULT, "import" ) );
result.add ( new MenuEntry ( "Maintenance", 160, "Export all channels", 300, LinkTarget.createFromController ( TransferController.class, "exportAll" ), Modifier.DEFAULT, "export" ) );
}
return result;
}
return null;
}
}