/*
* JetS3t : Java S3 Toolkit
* Project hosted at http://bitbucket.org/jmurty/jets3t/
*
* Copyright 2007 James Murty
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jets3t.apps.cockpitlite;
import java.awt.CardLayout;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.datatransfer.DataFlavor;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.LookAndFeel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableCellRenderer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.InvalidCredentialsException;
import org.apache.http.auth.NTCredentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.jets3t.gui.AuthenticationDialog;
import org.jets3t.gui.ErrorDialog;
import org.jets3t.gui.GuiUtils;
import org.jets3t.gui.HyperlinkActivatedListener;
import org.jets3t.gui.ItemPropertiesDialog;
import org.jets3t.gui.ProgressDialog;
import org.jets3t.gui.ProgressPanel;
import org.jets3t.gui.TableSorter;
import org.jets3t.gui.UserInputFields;
import org.jets3t.gui.skins.SkinsFactory;
import org.jets3t.service.Constants;
import org.jets3t.service.Jets3tProperties;
import org.jets3t.service.S3ServiceException;
import org.jets3t.service.acl.AccessControlList;
import org.jets3t.service.acl.GrantAndPermission;
import org.jets3t.service.acl.GroupGrantee;
import org.jets3t.service.acl.Permission;
import org.jets3t.service.impl.rest.httpclient.RestS3Service;
import org.jets3t.service.io.BytesProgressWatcher;
import org.jets3t.service.model.S3Bucket;
import org.jets3t.service.model.S3Object;
import org.jets3t.service.multithread.CancelEventTrigger;
import org.jets3t.service.multithread.CopyObjectsEvent;
import org.jets3t.service.multithread.CreateBucketsEvent;
import org.jets3t.service.multithread.CreateObjectsEvent;
import org.jets3t.service.multithread.DeleteObjectsEvent;
import org.jets3t.service.multithread.DeleteVersionedObjectsEvent;
import org.jets3t.service.multithread.DownloadObjectsEvent;
import org.jets3t.service.multithread.DownloadPackage;
import org.jets3t.service.multithread.GetObjectHeadsEvent;
import org.jets3t.service.multithread.GetObjectsEvent;
import org.jets3t.service.multithread.ListObjectsEvent;
import org.jets3t.service.multithread.LookupACLEvent;
import org.jets3t.service.multithread.S3ServiceEventListener;
import org.jets3t.service.multithread.S3ServiceMulti;
import org.jets3t.service.multithread.ServiceEvent;
import org.jets3t.service.multithread.ThreadWatcher;
import org.jets3t.service.multithread.UpdateACLEvent;
import org.jets3t.service.utils.ByteFormatter;
import org.jets3t.service.utils.FileComparer;
import org.jets3t.service.utils.FileComparerResults;
import org.jets3t.service.utils.ObjectUtils;
import org.jets3t.service.utils.ServiceUtils;
import org.jets3t.service.utils.TimeFormatter;
import org.jets3t.service.utils.gatekeeper.GatekeeperMessage;
import org.jets3t.service.utils.gatekeeper.SignatureRequest;
import org.jets3t.service.utils.signedurl.GatekeeperClientUtils;
import org.jets3t.service.utils.signedurl.SignedUrlAndObject;
import contribs.com.centerkey.utils.BareBonesBrowserLaunch;
/**
* CockpitLite is a graphical Java application for viewing and managing the
* contents of an Amazon S3 account, where the S3 account is not owned by the
* application's user directly but is made available by a service provider.
* The service provider uses the Gatekeeper application to mediate the user's
* access to the S3 account, authorizing each of the user's interactions before
* it can be executed.
* <p>
* <a href="http://www.jets3t.org/applications/cockpitlite.html">CockpitLite Guide</a>.
* <p>
* This is the CockpitLite application class; it may be run as a stand-alone
* application or as an Applet.
*
* @author jmurty
*/
public class CockpitLite extends JApplet implements S3ServiceEventListener, ActionListener,
ListSelectionListener, HyperlinkActivatedListener, CredentialsProvider {
private static final long serialVersionUID = 4969295009540293079L;
private static final Log log = LogFactory.getLog(CockpitLite.class);
private static final String PROPERTIES_FILENAME = "cockpitlite.properties";
public static final String APPLICATION_DESCRIPTION = "Cockpit Lite/" + Constants.JETS3T_VERSION;
public static final String APPLICATION_TITLE = "JetS3t Cockpit Lite";
private final Insets insetsZero = new Insets(0, 0, 0, 0);
private final Insets insetsDefault = new Insets(5, 7, 5, 7);
private final ByteFormatter byteFormatter = new ByteFormatter();
private final ByteFormatter byteFormatterTerse = new ByteFormatter("G","M","K","B",1);
private final TimeFormatter timeFormatterTerse = new TimeFormatter("h","h","m","m","s","s");
private final SimpleDateFormat yearAndTimeSDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private final SimpleDateFormat timeSDF = new SimpleDateFormat("HH:mm:ss");
private final GuiUtils guiUtils = new GuiUtils();
private Jets3tProperties cockpitLiteProperties = null;
private static final String ACL_PRIVATE_DESCRIPTION = "Private";
private static final String ACL_PUBLIC_DESCRIPTION = "Public";
private static final String ACL_UNKNOWN_DESCRIPTION = "?";
/**
* Properties set in stand-alone application from the command line arguments.
*/
private Properties standAloneArgumentProperties = null;
/**
* Stores the active ProgressPanel objects that track event progress.
*/
private final Map progressPanelMap = new HashMap();
private final Object lock = new Object();
/**
* Multi-threaded S3 service used by the application.
*/
private S3ServiceMulti s3ServiceMulti = null;
private GatekeeperClientUtils gkClient = null;
private final CredentialsProvider mCredentialProvider;
private String userBucketName = null;
private String userVanityHost = null;
private String userPath = "";
private boolean userCanUpload = false;
private boolean userCanDownload = false;
private boolean userCanDelete = false;
private boolean userCanACL = false;
private boolean isRunningAsApplet = false;
private Frame ownerFrame = null;
private boolean isStandAloneApplication = false;
private SkinsFactory skinsFactory = null;
/*
* HTTP connection settings for communication *with Gatekeeper only*, the
* S3 connection parameters are set in the jets3t.properties file.
*/
public static final int HTTP_CONNECTION_TIMEOUT = 60000;
public static final int SOCKET_CONNECTION_TIMEOUT = 60000;
public static final int MAX_CONNECTION_RETRIES = 5;
private JPanel stackPanel = null;
private CardLayout stackPanelCardLayout = null;
// Object main menu items
private JPopupMenu objectActionMenu = null;
private JMenuItem viewObjectPropertiesMenuItem = null;
private JMenuItem refreshObjectMenuItem = null;
private JMenuItem togglePublicMenuItem = null;
private JMenuItem downloadObjectMenuItem = null;
private JMenuItem uploadFilesMenuItem = null;
private JMenuItem generatePublicGetUrl = null;
private JMenuItem deleteObjectMenuItem = null;
// Login panel items
private JPanel loginPanel = null;
private JButton loginButton = null;
// Objects table
private JLabel objectsHeadingLabel = null;
private JTable objectsTable = null;
private JScrollPane objectsTableSP = null;
private CLObjectTableModel objectTableModel = null;
private TableSorter objectTableModelSorter = null;
// Progress notification aea
private JPanel progressNotificationPanel = null;
private JLabel objectsSummaryLabel = null;
private ProgressDialog progressDialog = null;
private UserInputFields userInputFields = null;
// Class variables used for uploading or downloading files.
private File downloadDirectory = null;
private Map downloadObjectsToFileMap = null;
private boolean isDownloadingObjects = false;
private boolean isUploadingFiles = false;
private Map filesAlreadyInDownloadDirectoryMap = null;
private Map s3DownloadObjectsMap = null;
private Map<String, String> objectKeyToFilepathMap = null;
private Map s3ExistingObjectsMap = null;
private File fileChoosersLastUploadDirectory = null;
private JPanel filterObjectsPanel = null;
private JCheckBox filterObjectsCheckBox = null;
private JTextField filterObjectsPrefix = null;
// File comparison options
private static final String UPLOAD_NEW_FILES_ONLY = "Only upload new file(s)";
private static final String UPLOAD_NEW_AND_CHANGED_FILES = "Upload new and changed file(s)";
private static final String UPLOAD_ALL_FILES = "Upload all files";
private static final String DOWNLOAD_NEW_FILES_ONLY = "Only download new file(s)";
private static final String DOWNLOAD_NEW_AND_CHANGED_FILES = "Download new and changed file(s)";
private static final String DOWNLOAD_ALL_FILES = "Download all files";
/**
* Flag used to indicate the "viewing objects" application state.
*/
private boolean isViewingObjectProperties = false;
/**
* Constructor to run this application as an Applet.
*/
public CockpitLite() {
mCredentialProvider = new BasicCredentialsProvider();
isRunningAsApplet = true;
}
/**
* Constructor to run this application in a stand-alone window.
*
* @param ownerFrame the frame the application will be displayed in
* @throws S3ServiceException
*/
public CockpitLite(JFrame ownerFrame, Properties standAloneArgumentProperties) throws S3ServiceException {
mCredentialProvider = new BasicCredentialsProvider();
this.ownerFrame = ownerFrame;
this.standAloneArgumentProperties = standAloneArgumentProperties;
isStandAloneApplication = true;
init();
ownerFrame.getContentPane().add(this);
ownerFrame.setBounds(this.getBounds());
ownerFrame.setVisible(true);
}
/**
* Prepares application to run as a GUI by finding/creating a root owner JFrame, creating an
* un-authenticated {@link RestS3Service} and loading properties files.
*/
@Override
public void init() {
super.init();
// Find or create a Frame to own modal dialog boxes.
if (this.ownerFrame == null) {
Component c = this;
while (!(c instanceof Frame) && c.getParent() != null) {
c = c.getParent();
}
if (!(c instanceof Frame)) {
this.ownerFrame = new JFrame();
} else {
this.ownerFrame = (Frame) c;
}
}
cockpitLiteProperties = Jets3tProperties.getInstance(PROPERTIES_FILENAME);
boolean isMissingRequiredInitProperty = false;
if (isRunningAsApplet) {
// Read parameters for Applet, based on names specified in the uploader properties.
String appletParamNames = cockpitLiteProperties.getStringProperty("applet.params", "");
StringTokenizer st = new StringTokenizer(appletParamNames, ",");
while (st.hasMoreTokens()) {
String paramName = st.nextToken();
String paramValue = this.getParameter(paramName);
// Fatal error if a parameter is missing.
if (null == paramValue) {
log.error("Missing required applet parameter: " + paramName);
isMissingRequiredInitProperty = true;
} else {
log.debug("Found applet parameter: " + paramName + "='" + paramValue + "'");
// Set params as properties in the central properties source for this application.
// Note that parameter values will over-write properties with the same name.
cockpitLiteProperties.setProperty(paramName, paramValue);
}
}
} else {
// Add application parameters properties.
if (standAloneArgumentProperties != null) {
Enumeration e = standAloneArgumentProperties.keys();
while (e.hasMoreElements()) {
String propName = (String) e.nextElement();
String propValue = standAloneArgumentProperties.getProperty(propName);
// Fatal error if a parameter is missing.
if (null == propValue) {
log.error("Missing required command-line property: " + propName);
isMissingRequiredInitProperty = true;
} else {
log.debug("Using command-line property: " + propName + "='" + propValue + "'");
// Set arguments as properties in the central properties source for this application.
// Note that argument values will over-write properties with the same name.
cockpitLiteProperties.setProperty(propName, propValue);
}
}
}
}
// Initialise the GUI.
initGui();
if (isMissingRequiredInitProperty) {
String message = "Missing one or more required application properties";
log.error(message);
ErrorDialog.showDialog(ownerFrame, this, cockpitLiteProperties.getProperties(), message, null);
System.exit(1);
}
String gatekeeperUrl = cockpitLiteProperties.getStringProperty("gatekeeperUrl", null);
if (gatekeeperUrl == null) {
String message = "Application properties file '" + PROPERTIES_FILENAME + "' is not available";
log.error(message);
ErrorDialog.showDialog(ownerFrame, this, cockpitLiteProperties.getProperties(), message, null);
System.exit(1);
}
gkClient = new GatekeeperClientUtils(
gatekeeperUrl,
APPLICATION_DESCRIPTION, MAX_CONNECTION_RETRIES, HTTP_CONNECTION_TIMEOUT,
this);
// Initialise a non-authenticated service.
// Revert to anonymous service.
s3ServiceMulti = new S3ServiceMulti(
new RestS3Service(null, APPLICATION_DESCRIPTION, this), this);
}
/**
* Initialises the application's GUI elements.
*/
private void initGui() {
// Initialise skins factory.
skinsFactory = SkinsFactory.getInstance(cockpitLiteProperties.getProperties());
// Set Skinned Look and Feel.
LookAndFeel lookAndFeel = skinsFactory.createSkinnedMetalTheme("SkinnedLookAndFeel");
try {
UIManager.setLookAndFeel(lookAndFeel);
} catch (UnsupportedLookAndFeelException e) {
log.error("Unable to set skinned LookAndFeel", e);
}
// Primary panel that contains all other items.
JPanel primaryPanel = skinsFactory.createSkinnedJPanel("PrimaryPanel");
primaryPanel.setLayout(new GridBagLayout());
this.getContentPane().add(primaryPanel);
// Setup the stack panel, which contains all other panels as a stack.
stackPanel = skinsFactory.createSkinnedJPanel("StackPanel");
stackPanelCardLayout = new CardLayout();
stackPanel.setLayout(stackPanelCardLayout);
primaryPanel.add(stackPanel,
new GridBagConstraints(0, 0, 1, 1, 1, 1, GridBagConstraints.CENTER, GridBagConstraints.BOTH, insetsZero, 0, 0));
// Progress notification panel
progressNotificationPanel = skinsFactory.createSkinnedJPanel("ProgressNotificationPanel");
progressNotificationPanel.setLayout(new GridBagLayout());
primaryPanel.add(progressNotificationPanel,
new GridBagConstraints(0, 1, 1, 1, 1, 0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
new Insets(5, 0, 5, 0), 0, 0));
int row = 0;
// Login panel.
row = 0;
loginPanel = skinsFactory.createSkinnedJPanel("LoginPanel");
loginPanel.setLayout(new GridBagLayout());
userInputFields = new UserInputFields(insetsDefault, null, skinsFactory);
userInputFields.buildFieldsPanel(loginPanel, cockpitLiteProperties);
loginButton = skinsFactory.createSkinnedJButton("LoginButton");
loginButton.setText("Log me in");
loginButton.addActionListener(this);
loginPanel.add(loginButton,
new GridBagConstraints(0, loginPanel.getComponentCount(), 1, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.NONE, insetsDefault, 0, 0));
// Filter panel.
filterObjectsPanel = skinsFactory.createSkinnedJPanel("FilterPanel");
filterObjectsPanel.setLayout(new GridBagLayout());
filterObjectsPrefix = skinsFactory.createSkinnedJTextField("FilterPrefix");
filterObjectsPrefix.setToolTipText("Only show files starting with this string");
filterObjectsPrefix.addActionListener(this);
filterObjectsPrefix.setActionCommand("RefreshObjects");
JLabel filterPrefixLabel = skinsFactory.createSkinnedJHtmlLabel("FilterPrefixLable", this);
filterPrefixLabel.setText("File name starts with: ");
filterObjectsPanel.add(filterPrefixLabel,
new GridBagConstraints(0, 0, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, insetsZero, 0, 0));
filterObjectsPanel.add(filterObjectsPrefix,
new GridBagConstraints(1, 0, 1, 1, 1, 0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, insetsDefault, 0, 0));
filterObjectsPanel.setVisible(false);
// Objects panel.
row = 0;
JPanel objectsPanel = skinsFactory.createSkinnedJPanel("ObjectsPanel");
objectsPanel.setLayout(new GridBagLayout());
filterObjectsCheckBox = skinsFactory.createSkinnedJCheckBox("FilterCheckbox");
filterObjectsCheckBox.setText("Search files");
filterObjectsCheckBox.setEnabled(true);
filterObjectsCheckBox.addActionListener(this);
filterObjectsCheckBox.setToolTipText("Check this option to search your files");
objectsHeadingLabel = skinsFactory.createSkinnedJHtmlLabel("ObjectsHeadingLabel", this);
objectsHeadingLabel.setText("Not logged in");
objectsPanel.add(objectsHeadingLabel,
new GridBagConstraints(0, row, 1, 1, 1, 0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, insetsZero, 0, 0));
objectsPanel.add(filterObjectsCheckBox,
new GridBagConstraints(1, row, 1, 1, 0, 0, GridBagConstraints.EAST, GridBagConstraints.HORIZONTAL, insetsZero, 0, 0));
JButton objectActionButton = skinsFactory.createSkinnedJButton("ObjectMenuButton");
objectActionButton.setToolTipText("File actions menu");
guiUtils.applyIcon(objectActionButton, "/images/nuvola/16x16/actions/misc.png");
objectActionButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JButton sourceButton = (JButton) e.getSource();
objectActionMenu.show(sourceButton, 0, sourceButton.getHeight());
}
});
objectsPanel.add(objectActionButton,
new GridBagConstraints(2, row, 1, 1, 0, 0, GridBagConstraints.EAST, GridBagConstraints.HORIZONTAL, insetsZero, 0, 0));
objectsPanel.add(filterObjectsPanel,
new GridBagConstraints(0, ++row, 3, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, insetsZero, 0, 0));
objectsTable = skinsFactory.createSkinnedJTable("ObjectsTable");
objectTableModel = new CLObjectTableModel();
objectTableModelSorter = new TableSorter(objectTableModel);
objectTableModelSorter.setTableHeader(objectsTable.getTableHeader());
objectsTable.setModel(objectTableModelSorter);
objectsTable.setDefaultRenderer(Long.class, new DefaultTableCellRenderer() {
private static final long serialVersionUID = 7229656175879985698L;
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
String formattedSize = byteFormatter.formatByteSize(((Long)value).longValue());
return super.getTableCellRendererComponent(table, formattedSize, isSelected, hasFocus, row, column);
}
});
objectsTable.setDefaultRenderer(Date.class, new DefaultTableCellRenderer() {
private static final long serialVersionUID = -4983176028291916397L;
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
Date date = (Date) value;
return super.getTableCellRendererComponent(table, yearAndTimeSDF.format(date), isSelected, hasFocus, row, column);
}
});
objectsTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
objectsTable.getSelectionModel().addListSelectionListener(this);
objectsTable.setShowHorizontalLines(true);
objectsTable.setShowVerticalLines(true);
objectsTable.addMouseListener(new ContextMenuListener());
objectsTableSP = skinsFactory.createSkinnedJScrollPane("ObjectsTableSP", objectsTable);
objectsPanel.add(objectsTableSP,
new GridBagConstraints(0, ++row, 3, 1, 1, 1, GridBagConstraints.CENTER, GridBagConstraints.BOTH, insetsZero, 0, 0));
objectsSummaryLabel = skinsFactory.createSkinnedJHtmlLabel("ObjectsSummary", this);
objectsSummaryLabel.setHorizontalAlignment(JLabel.CENTER);
objectsSummaryLabel.setFocusable(false);
objectsPanel.add(objectsSummaryLabel,
new GridBagConstraints(0, ++row, 3, 1, 1, 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, insetsDefault, 0, 0));
// Object action menu.
objectActionMenu = skinsFactory.createSkinnedJPopupMenu("ObjectPopupMenu");
refreshObjectMenuItem = skinsFactory.createSkinnedJMenuItem("RefreshMenuItem");
refreshObjectMenuItem.setText("Refresh file listing");
refreshObjectMenuItem.setActionCommand("RefreshObjects");
refreshObjectMenuItem.addActionListener(this);
guiUtils.applyIcon(refreshObjectMenuItem, "/images/nuvola/16x16/actions/reload.png");
objectActionMenu.add(refreshObjectMenuItem);
viewObjectPropertiesMenuItem = skinsFactory.createSkinnedJMenuItem("PropertiesMenuItem");
viewObjectPropertiesMenuItem.setText("View file properties...");
viewObjectPropertiesMenuItem.setActionCommand("ViewObjectProperties");
viewObjectPropertiesMenuItem.addActionListener(this);
guiUtils.applyIcon(viewObjectPropertiesMenuItem, "/images/nuvola/16x16/actions/viewmag.png");
objectActionMenu.add(viewObjectPropertiesMenuItem);
downloadObjectMenuItem = skinsFactory.createSkinnedJMenuItem("DownloadMenuItem");
downloadObjectMenuItem.setText("Download file(s)...");
downloadObjectMenuItem.setActionCommand("DownloadObjects");
downloadObjectMenuItem.addActionListener(this);
guiUtils.applyIcon(downloadObjectMenuItem, "/images/nuvola/16x16/actions/1downarrow.png");
objectActionMenu.add(downloadObjectMenuItem);
uploadFilesMenuItem = skinsFactory.createSkinnedJMenuItem("UploadMenuItem");
uploadFilesMenuItem.setText("Upload file(s)...");
uploadFilesMenuItem.setActionCommand("UploadFiles");
uploadFilesMenuItem.addActionListener(this);
guiUtils.applyIcon(uploadFilesMenuItem, "/images/nuvola/16x16/actions/1uparrow.png");
objectActionMenu.add(uploadFilesMenuItem);
objectActionMenu.add(new JSeparator());
togglePublicMenuItem = skinsFactory.createSkinnedJMenuItem("AclToggleMenuItem");
togglePublicMenuItem.setText("Change privacy setting...");
togglePublicMenuItem.setActionCommand("TogglePublicPrivate");
togglePublicMenuItem.addActionListener(this);
guiUtils.applyIcon(togglePublicMenuItem, "/images/nuvola/16x16/actions/encrypted.png");
objectActionMenu.add(togglePublicMenuItem);
generatePublicGetUrl = skinsFactory.createSkinnedJMenuItem("PublicUrlMenuItem");
generatePublicGetUrl.setText("Public web link...");
generatePublicGetUrl.setActionCommand("GeneratePublicGetURL");
generatePublicGetUrl.addActionListener(this);
guiUtils.applyIcon(generatePublicGetUrl, "/images/nuvola/16x16/actions/wizard.png");
objectActionMenu.add(generatePublicGetUrl);
objectActionMenu.add(new JSeparator());
deleteObjectMenuItem = skinsFactory.createSkinnedJMenuItem("DeleteMenuItem");
deleteObjectMenuItem.setText("Delete file(s)...");
deleteObjectMenuItem.setActionCommand("DeleteObjects");
deleteObjectMenuItem.addActionListener(this);
guiUtils.applyIcon(deleteObjectMenuItem, "/images/nuvola/16x16/actions/cancel.png");
objectActionMenu.add(deleteObjectMenuItem);
viewObjectPropertiesMenuItem.setEnabled(false);
refreshObjectMenuItem.setEnabled(false);
togglePublicMenuItem.setEnabled(false);
downloadObjectMenuItem.setEnabled(false);
generatePublicGetUrl.setEnabled(false);
deleteObjectMenuItem.setEnabled(false);
// Card layout in stack panel
stackPanel.add(loginPanel, "LoginPanel");
stackPanel.add(objectsPanel, "ObjectsPanel");
// Set preferred sizes
int preferredWidth = 800;
int preferredHeight = 600;
this.setBounds(new Rectangle(new Dimension(preferredWidth, preferredHeight)));
// Initialize drop target.
initDropTarget(new JComponent[] {objectsPanel} );
objectsPanel.getDropTarget().setActive(true);
}
/**
* Initialise the application's File drop targets for drag and drop copying of local files
* to S3.
*
* @param dropTargetComponents
* the components files can be dropped on to transfer them to S3
*/
private void initDropTarget(JComponent[] dropTargetComponents) {
DropTargetListener dropTargetListener = new DropTargetListener() {
private boolean checkValidDrag(DropTargetDragEvent dtde) {
if (dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor)
&& (DnDConstants.ACTION_COPY == dtde.getDropAction()
|| DnDConstants.ACTION_MOVE == dtde.getDropAction()))
{
dtde.acceptDrag(dtde.getDropAction());
return true;
} else {
dtde.rejectDrag();
return false;
}
}
public void dragEnter(DropTargetDragEvent dtde) {
if (checkValidDrag(dtde)) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
objectsTable.requestFocusInWindow();
};
});
}
}
public void dragOver(DropTargetDragEvent dtde) {
checkValidDrag(dtde);
}
public void dropActionChanged(DropTargetDragEvent dtde) {
if (checkValidDrag(dtde)) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
objectsTable.requestFocusInWindow();
};
});
} else {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
ownerFrame.requestFocusInWindow();
};
});
}
}
public void dragExit(DropTargetEvent dte) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
ownerFrame.requestFocusInWindow();
};
});
}
public void drop(DropTargetDropEvent dtde) {
if (dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor)
&& (DnDConstants.ACTION_COPY == dtde.getDropAction()
|| DnDConstants.ACTION_MOVE == dtde.getDropAction()))
{
dtde.acceptDrop(dtde.getDropAction());
try {
final List fileList = (List) dtde.getTransferable().getTransferData(
DataFlavor.javaFileListFlavor);
if (fileList != null && fileList.size() > 0) {
new Thread() {
@Override
public void run() {
prepareForFilesUpload((File[]) fileList.toArray(new File[fileList.size()]));
}
}.start();
}
} catch (Exception e) {
String message = "Unable to start accept dropped item(s)";
log.error(message, e);
ErrorDialog.showDialog(ownerFrame, null, cockpitLiteProperties.getProperties(), message, e);
}
} else {
dtde.rejectDrop();
}
}
};
// Attach drop target listener to each target component.
for (int i = 0; i < dropTargetComponents.length; i++) {
new DropTarget(dropTargetComponents[i], DnDConstants.ACTION_COPY, dropTargetListener, true);
}
}
/**
* Starts a progress display dialog. While the dialog is running the user cannot interact
* with the application, except to cancel the task.
*
* @param statusMessage
* describes the status of a task text meaningful to the user, such as "3 files of 7 uploaded"
* @param detailsText
* describes the status of a task in more detail, such as the current transfer rate and Time remaining.
* @param minTaskValue the minimum progress value for a task, generally 0
* @param maxTaskValue
* the maximum progress value for a task, such as the total number of threads or 100 if
* using percentage-complete as a metric.
* @param cancelEventListener
* listener that is responsible for cancelling a long-lived task when the user clicks
* the cancel button. If a task cannot be cancelled this must be null.
* @param cancelButtonText
* text displayed in the cancel button if a task can be cancelled. This is only used if
* a cancel event listener is provided.
*/
private void startProgressDialog(final String statusMessage, final String detailsText,
final int minTaskValue, final int maxTaskValue, final String cancelButtonText,
final CancelEventTrigger cancelEventListener)
{
if (this.progressDialog == null) {
this.progressDialog = new ProgressDialog(
this.ownerFrame, "Please wait...", cockpitLiteProperties.getProperties());
}
this.getContentPane().setCursor(new Cursor(Cursor.WAIT_CURSOR));
SwingUtilities.invokeLater(new Runnable() {
public void run() {
progressDialog.startDialog(statusMessage, detailsText, minTaskValue, maxTaskValue,
cancelEventListener, cancelButtonText);
}
});
}
/**
* Updates the status text and value of the progress display dialog.
* @param statusMessage
* describes the status of a task text meaningful to the user, such as "3 files of 7 uploaded"
* @param detailsText
* describes the status of a task in more detail, such as the current transfer rate and time remaining.
* @param progressValue
* value representing how far through the task we are (relative to min and max values)
*/
private void updateProgressDialog(final String statusMessage, final String detailsText, final int progressValue) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
progressDialog.updateDialog(statusMessage, detailsText, progressValue);
}
});
}
/**
* Stops/halts the progress display dialog and allows the user to interact with the application.
*/
private void stopProgressDialog() {
this.getContentPane().setCursor(null);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
progressDialog.stopDialog();
}
});
}
protected void startProgressPanel(Object operationId, String statusMessage,
int maxCount, CancelEventTrigger cancelEventTrigger)
{
// Create new progress panel.
final ProgressPanel progressPanel = new ProgressPanel(
cockpitLiteProperties.getProperties(), cancelEventTrigger);
progressPanel.startProgress(statusMessage, 0, maxCount);
// Store this panel against the operation ID it tracks.
progressPanelMap.put(operationId, progressPanel);
// Display panel in progress notification area.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
progressNotificationPanel.add(progressPanel,
new GridBagConstraints(0, progressNotificationPanel.getComponents().length,
1, 1, 1, 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, insetsZero, 0, 0));
progressNotificationPanel.revalidate();
}
});
}
protected void updateProgressPanel(Object operationId, final String statusMessage,
final int currentCount)
{
// Retrieve progress panel.
final ProgressPanel progressPanel = (ProgressPanel) progressPanelMap.get(operationId);
if (progressPanel != null) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
progressPanel.updateProgress(statusMessage, currentCount);
}
});
}
}
protected void stopProgressPanel(Object operationId) {
// Retrieve progress panel.
final ProgressPanel progressPanel = (ProgressPanel) progressPanelMap.get(operationId);
if (progressPanel != null) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
progressNotificationPanel.remove(progressPanel);
progressNotificationPanel.revalidate();
progressPanelMap.remove(progressPanel);
progressPanel.dispose();
}
});
}
}
/**
* Event handler for this application, handles all menu items.
*/
public void actionPerformed(ActionEvent event) {
if (event.getSource().equals(loginButton)) {
new Thread() {
@Override
public void run() {
listObjects();
}
}.start();
}
// Object Events
else if ("ViewObjectProperties".equals(event.getActionCommand())) {
listObjectProperties();
} else if ("RefreshObjects".equals(event.getActionCommand())) {
new Thread() {
@Override
public void run() {
listObjects();
}
}.start();
} else if ("TogglePublicPrivate".equals(event.getActionCommand())) {
new Thread() {
@Override
public void run() {
S3Object object = getSelectedObjects()[0];
String aclStatus = objectTableModel.getObjectAclStatus(object);
boolean originalAclWasPublic = ACL_PUBLIC_DESCRIPTION.equals(aclStatus);
ToggleAclDialog dialog = new ToggleAclDialog(ownerFrame,
originalAclWasPublic, null, cockpitLiteProperties.getProperties());
dialog.setVisible(true);
// Update ACL setting.
S3Object minimalObject = new S3Object(object.getKey());
AccessControlList newAcl = (dialog.isPublicAclSet()
? AccessControlList.REST_CANNED_PUBLIC_READ
: AccessControlList.REST_CANNED_PRIVATE);
if (newAcl != null) {
if (AccessControlList.REST_CANNED_PRIVATE.equals(newAcl)) {
minimalObject.addMetadata(Constants.REST_HEADER_PREFIX + "acl", "private");
} else if (AccessControlList.REST_CANNED_PUBLIC_READ.equals(newAcl)) {
minimalObject.addMetadata(Constants.REST_HEADER_PREFIX + "acl", "public-read");
}
}
updateObjectsAccessControlLists(
new S3Object[] {minimalObject}, newAcl);
dialog.dispose();
}
}.start();
} else if ("GeneratePublicGetURL".equals(event.getActionCommand())) {
generatePublicGetUrl();
} else if ("DeleteObjects".equals(event.getActionCommand())) {
deleteSelectedObjects();
} else if ("DownloadObjects".equals(event.getActionCommand())) {
try {
downloadSelectedObjects();
} catch (Exception ex) {
String message = "Unable to download objects from S3";
log.error(message, ex);
ErrorDialog.showDialog(ownerFrame, this, cockpitLiteProperties.getProperties(), message, ex);
}
} else if ("UploadFiles".equals(event.getActionCommand())) {
JFileChooser fileChooser = new JFileChooser();
fileChooser.setMultiSelectionEnabled(true);
fileChooser.setDialogTitle("Choose file(s) to upload");
fileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
fileChooser.setApproveButtonText("Upload files");
fileChooser.setCurrentDirectory(fileChoosersLastUploadDirectory);
int returnVal = fileChooser.showOpenDialog(ownerFrame);
if (returnVal != JFileChooser.APPROVE_OPTION) {
return;
}
final File[] uploadFiles = fileChooser.getSelectedFiles();
if (uploadFiles.length == 0) {
return;
}
// Save the chosen directory location for next time.
fileChoosersLastUploadDirectory = uploadFiles[0].getParentFile();
new Thread() {
@Override
public void run() {
prepareForFilesUpload(uploadFiles);
}
}.start();
} else if (event.getSource().equals(filterObjectsCheckBox)) {
if (filterObjectsCheckBox.isSelected()) {
filterObjectsPanel.setVisible(true);
} else {
filterObjectsPanel.setVisible(false);
filterObjectsPrefix.setText("");
}
}
// Ooops...
else {
log.warn("Unrecognised ActionEvent command '" + event.getActionCommand() + "' in " + event);
}
}
/**
* Handles list selection events for this application.
*/
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting()) {
return;
}
if (e.getSource().equals(objectsTable.getSelectionModel())) {
objectSelectedAction();
}
}
private void listObjects() {
try {
// Obtain login details from application's login screen and store them in
// the application properties so the details will be forwarded to the Gatekeeper
// with each request.
Properties loginProperties = userInputFields.getUserInputsAsProperties(true);
Iterator iter = loginProperties.keySet().iterator();
while (iter.hasNext()) {
String propertyName = (String) iter.next();
String propertyValue = loginProperties.getProperty(propertyName);
cockpitLiteProperties.setProperty(propertyName, propertyValue);
}
startProgressPanel(this, "Finding files", 0, null);
// Perform object listing operation via Gatekeeper.
Map requestProperties = new HashMap();
requestProperties.put(GatekeeperMessage.LIST_OBJECTS_IN_BUCKET_FLAG, "");
requestProperties.putAll(cockpitLiteProperties.getProperties());
if (filterObjectsCheckBox.isSelected() && filterObjectsPrefix.getText().length() > 0) {
requestProperties.put("Prefix", filterObjectsPrefix.getText());
}
GatekeeperMessage responseMessage =
gkClient.requestActionThroughGatekeeper(
null, null, new S3Object[] {}, requestProperties);
stopProgressPanel(this);
String gatekeeperErrorCode = responseMessage.getApplicationProperties()
.getProperty(GatekeeperMessage.APP_PROPERTY_GATEKEEPER_ERROR_CODE);
if (gatekeeperErrorCode == null) {
// Listing succeeded
final S3Object[] objects = gkClient.buildS3ObjectsFromSignatureRequests(
responseMessage.getSignatureRequests());
// User account description provided by Gatekeeper
final String accountDescription =
responseMessage.getApplicationProperties().getProperty("AccountDescription");
// User's settings
userCanUpload = "true".equalsIgnoreCase(
responseMessage.getApplicationProperties().getProperty("UserCanUpload"));
userCanDownload = "true".equalsIgnoreCase(
responseMessage.getApplicationProperties().getProperty("UserCanDownload"));
userCanDelete = "true".equalsIgnoreCase(
responseMessage.getApplicationProperties().getProperty("UserCanDelete"));
userCanACL = "true".equalsIgnoreCase(
responseMessage.getApplicationProperties().getProperty("UserCanACL"));
userBucketName = responseMessage.getApplicationProperties().getProperty("S3BucketName");
userPath = responseMessage.getApplicationProperties().getProperty("UserPath", "");
userVanityHost = responseMessage.getApplicationProperties().getProperty("UserVanityHost");
objectTableModel.setUsersPath(userPath);
uploadFilesMenuItem.setEnabled(userCanUpload);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
objectsHeadingLabel.setText(
(accountDescription != null ? accountDescription : "Logged in"));
objectTableModel.removeAllObjects();
objectTableModel.addObjects(objects);
updateObjectsSummary();
refreshObjectMenuItem.setEnabled(true);
lookupObjectsAccessControlLists(objects);
}
});
stackPanelCardLayout.show(stackPanel, "ObjectsPanel");
} else {
// Listing failed
ErrorDialog.showDialog(ownerFrame, this, cockpitLiteProperties.getProperties(),
"Your log-in information was not correct, please try again", null);
}
} catch (Exception e) {
stopProgressPanel(this);
log.error("Gatekeeper login failed for URL: " + gkClient.getGatekeeperUrl(), e);
ErrorDialog.showDialog(ownerFrame, this, cockpitLiteProperties.getProperties(),
"Log-in failed, please try again", e);
}
}
/**
* Displays the currently selected object's properties in the dialog {@link ItemPropertiesDialog}.
* <p>
* As detailed information about the object may not yet be available, this method works
* indirectly via the {@link #retrieveObjectsDetails} method. The <code>retrieveObjectsDetails</code>
* method retrieves all the details for the currently selected objects, and once they are available
* knows to display the <code>PropertiesDialog</code> as the {@link #isViewingObjectProperties} flag
* is set.
*/
private void listObjectProperties() {
isViewingObjectProperties = true;
retrieveObjectsDetails(getSelectedObjects());
}
/**
* This method is an {@link S3ServiceEventListener} action method that is invoked when this
* application's <code>S3ServiceMulti</code> triggers a <code>GetObjectsEvent</code>.
* <p>
* This never happens in this application as downloads are performed by
* {@link S3ServiceMulti#downloadObjects(S3Bucket, DownloadPackage[])} instead.
*
* @param event
*/
public void s3ServiceEventPerformed(GetObjectsEvent event) {
// Not used.
}
/**
* This method is an {@link S3ServiceEventListener} action method that is invoked when this
* application's <code>S3ServiceMulti</code> triggers a <code>ListObjectsEvent</code>.
* <p>
* This never happens in this application as it does not perform multi-threaded object
* listings.
*
* @param event
*/
public void s3ServiceEventPerformed(ListObjectsEvent event) {
// Not used.
}
public void s3ServiceEventPerformed(DeleteVersionedObjectsEvent event) {
// Not used.
}
/**
* Actions performed when an object is selected in the objects list table.
*/
private void objectSelectedAction() {
S3Object[] selectedObjects = getSelectedObjects();
int count = selectedObjects.length;
togglePublicMenuItem.setEnabled(
userCanACL && count == 1);
downloadObjectMenuItem.setEnabled(
userCanDownload && count > 0);
deleteObjectMenuItem.setEnabled(
userCanDelete && count > 0);
viewObjectPropertiesMenuItem.setEnabled(count > 0);
generatePublicGetUrl.setEnabled(
count == 1
&& ACL_PUBLIC_DESCRIPTION.equals(
objectTableModel.getObjectAclStatus(selectedObjects[0])));
}
/**
* Updates the summary text shown below the listing of objects, which details the
* number and total size of the objects.
*
*/
private void updateObjectsSummary() {
S3Object[] objects = objectTableModel.getObjects();
try {
String summary = "Please select a bucket";
long totalBytes = 0;
if (objects != null) {
summary = "<html>" + objects.length + " item" + (objects.length != 1? "s" : "");
for (int i = 0; i < objects.length; i++) {
totalBytes += objects[i].getContentLength();
}
if (totalBytes > 0) {
summary += ", " + byteFormatter.formatByteSize(totalBytes);
}
summary += " @ " + timeSDF.format(new Date());
if (isObjectFilteringActive()) {
summary += " - <i>Search results</i>";
}
summary += "</html>";
}
objectsSummaryLabel.setText(summary);
} catch (Throwable t) {
String message = "Unable to update object list summary";
log.error(message, t);
ErrorDialog.showDialog(ownerFrame, this, cockpitLiteProperties.getProperties(), message, t);
}
}
/**
* Displays object-specific actions in a popup menu.
* @param invoker the component near which the popup menu will be displayed
* @param xPos the mouse's horizontal co-ordinate when the popup menu was invoked
* @param yPos the mouse's vertical co-ordinate when the popup menu was invoked
*/
private void showObjectPopupMenu(JComponent invoker, int xPos, int yPos) {
if (getSelectedObjects().length == 0) {
return;
}
objectActionMenu.show(invoker, xPos, yPos);
}
/**
* @return the set of objects currently selected in the gui, or an empty array if none are selected.
*/
private S3Object[] getSelectedObjects() {
int viewRows[] = objectsTable.getSelectedRows();
if (viewRows.length == 0) {
return new S3Object[] {};
} else {
S3Object objects[] = new S3Object[viewRows.length];
for (int i = 0; i < viewRows.length; i++) {
int modelRow = objectTableModelSorter.modelIndex(viewRows[i]);
objects[i] = objectTableModel.getObject(modelRow);
}
return objects;
}
}
/**
* Retrieves ACL settings for the currently selected objects. The actual action is performed
* in the <code>s3ServiceEventPerformed</code> method specific to <code>LookupACLEvent</code>s.
*
*/
private void lookupObjectsAccessControlLists(final S3Object[] objects) {
(new Thread() {
@Override
public void run() {
try {
SignatureRequest[] signatureRequests = requestSignedRequests(
SignatureRequest.SIGNATURE_TYPE_ACL_LOOKUP, objects);
if (signatureRequests != null) {
String[] signedRequests = new String[signatureRequests.length];
for (int i = 0; i < signedRequests.length; i++) {
signedRequests[i] = signatureRequests[i].getSignedUrl();
}
s3ServiceMulti.getObjectsACLs(signedRequests);
} else {
// Signature request failed
ErrorDialog.showDialog(ownerFrame, null, cockpitLiteProperties.getProperties(),
"Sorry, you do not have the permission to view object privacy settings", null);
}
} catch (Exception e) {
log.error("Gatekeeper permissions check failed", e);
ErrorDialog.showDialog(ownerFrame, null, cockpitLiteProperties.getProperties(),
"Permissions check failed, please try again", e);
}
}
}).start();
}
/**
* This method is an {@link S3ServiceEventListener} action method that is invoked when this
* application's <code>S3ServiceMulti</code> triggers a <code>LookupACLEvent</code>.
*
* @param event
*/
public void s3ServiceEventPerformed(final LookupACLEvent event) {
if (ServiceEvent.EVENT_STARTED == event.getEventCode()) {
int threadCount = (int) event.getThreadWatcher().getThreadCount();
startProgressPanel(event.getUniqueOperationId(), "Privacy lookup 0/" + threadCount,
threadCount, event.getThreadWatcher().getCancelEventListener());
}
else if (ServiceEvent.EVENT_IN_PROGRESS == event.getEventCode()) {
int threadCount = (int) event.getThreadWatcher().getThreadCount();
int threadsCompleted = (int) event.getThreadWatcher().getCompletedThreads();
updateProgressPanel(event.getUniqueOperationId(),
"Privacy lookup " + threadsCompleted + "/" + threadCount, threadsCompleted);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
S3Object[] objectsWithAcl = event.getObjectsWithACL();
for (int i = 0; i < objectsWithAcl.length; i++) {
String aclStatus = getAclDescription(objectsWithAcl[i].getAcl());
objectTableModel.updateObjectAclStatus(objectsWithAcl[i], aclStatus);
}
}
});
}
else if (ServiceEvent.EVENT_COMPLETED == event.getEventCode()) {
stopProgressPanel(event.getUniqueOperationId());
}
else if (ServiceEvent.EVENT_CANCELLED == event.getEventCode()) {
stopProgressPanel(event.getUniqueOperationId());
}
else if (ServiceEvent.EVENT_ERROR == event.getEventCode()) {
stopProgressPanel(event.getUniqueOperationId());
String message = "Unable to lookup Access Control list for object(s)";
log.error(message, event.getErrorCause());
}
}
/**
* Updates ACL settings for the currently selected objects. The actual action is performed
* in the <code>s3ServiceEventPerformed</code> method specific to <code>UpdateACLEvent</code>s.
*
*/
private void updateObjectsAccessControlLists(final S3Object[] objectsToUpdate, final AccessControlList acl) {
(new Thread() {
@Override
public void run() {
try {
SignatureRequest[] signatureRequests = requestSignedRequests(
SignatureRequest.SIGNATURE_TYPE_ACL_UPDATE, objectsToUpdate);
if (signatureRequests != null) {
String[] signedRequests = new String[signatureRequests.length];
for (int i = 0; i < signedRequests.length; i++) {
signedRequests[i] = signatureRequests[i].getSignedUrl();
}
s3ServiceMulti.putObjectsACLs(signedRequests, acl);
} else {
// Listing failed
ErrorDialog.showDialog(ownerFrame, null, cockpitLiteProperties.getProperties(),
"Sorry, you do not have the permission to change object privacy settings", null);
}
} catch (Exception e) {
log.error("Gatekeeper permissions check failed", e);
ErrorDialog.showDialog(ownerFrame, null, cockpitLiteProperties.getProperties(),
"Permissions check failed, please try again", e);
}
}
}).start();
}
/**
* This method is an {@link S3ServiceEventListener} action method that is invoked when this
* application's <code>S3ServiceMulti</code> triggers a <code>UpdateACLEvent</code>.
* <p>
* This method merely updates the progress dialog as ACLs are updated.
*
* @param event
*/
public void s3ServiceEventPerformed(final UpdateACLEvent event) {
if (ServiceEvent.EVENT_STARTED == event.getEventCode()) {
startProgressPanel(event.getUniqueOperationId(),
"Privacy update 0/" + event.getThreadWatcher().getThreadCount(),
(int) event.getThreadWatcher().getThreadCount(),
event.getThreadWatcher().getCancelEventListener());
}
else if (ServiceEvent.EVENT_IN_PROGRESS == event.getEventCode()) {
ThreadWatcher progressStatus = event.getThreadWatcher();
String statusText = "Privacy update " + progressStatus.getCompletedThreads()
+ "/" + progressStatus.getThreadCount();
updateProgressPanel(event.getUniqueOperationId(), statusText,
(int) progressStatus.getCompletedThreads());
SwingUtilities.invokeLater(new Runnable() {
public void run() {
S3Object[] objects = event.getObjectsWithUpdatedACL();
for (int i = 0; i < objects.length; i++) {
String aclStatus = getAclDescription(objects[i].getAcl());
objectTableModel.updateObjectAclStatus(objects[i], aclStatus);
objectSelectedAction();
}
}
});
}
else if (ServiceEvent.EVENT_COMPLETED == event.getEventCode()) {
stopProgressPanel(event.getUniqueOperationId());
}
else if (ServiceEvent.EVENT_CANCELLED == event.getEventCode()) {
stopProgressPanel(event.getUniqueOperationId());
}
else if (ServiceEvent.EVENT_ERROR == event.getEventCode()) {
stopProgressPanel(event.getUniqueOperationId());
String message = "Unable to update Access Control List(s)";
log.error(message, event.getErrorCause());
ErrorDialog.showDialog(ownerFrame, this, cockpitLiteProperties.getProperties(), message, event.getErrorCause());
}
}
/**
* Prepares to perform a download of objects from S3 by prompting the user for a directory
* to store the files in, then performing the download.
*
* @throws IOException
*/
private void downloadSelectedObjects() throws IOException {
// Prompt user to choose directory location for downloaded files (or cancel download altogether)
JFileChooser fileChooser = new JFileChooser();
fileChooser.setDialogTitle("Choose directory to save S3 files in");
fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
fileChooser.setMultiSelectionEnabled(false);
fileChooser.setSelectedFile(downloadDirectory);
int returnVal = fileChooser.showDialog(ownerFrame, "Choose Directory");
if (returnVal != JFileChooser.APPROVE_OPTION) {
return;
}
downloadDirectory = fileChooser.getSelectedFile();
prepareForObjectsDownload();
}
private void prepareForObjectsDownload() {
// Build map of existing local files.
Map<String, String> objectKeyToFilepathMap = null;
try {
boolean storeEmptyDirectories = Jets3tProperties.getInstance(Constants.JETS3T_PROPERTIES_FILENAME)
.getBoolProperty("uploads.storeEmptyDirectories", true);
objectKeyToFilepathMap = FileComparer.getInstance()
.buildObjectKeyToFilepathMap(downloadDirectory.listFiles(), "", storeEmptyDirectories);
} catch (Exception e) {
String message = "Unable to review files in targetted download directory";
log.error(message, e);
ErrorDialog.showDialog(ownerFrame, this, cockpitLiteProperties.getProperties(), message, e);
return;
}
filesAlreadyInDownloadDirectoryMap = new HashMap();
// Build map of S3 Objects being downloaded.
s3DownloadObjectsMap = FileComparer.getInstance()
.populateObjectMap("", getSelectedObjects());
// Identify objects that may clash with existing files, or may be directories,
// and retrieve details for these.
ArrayList potentialClashingObjects = new ArrayList();
Set existingFilesObjectKeys = objectKeyToFilepathMap.keySet();
Iterator objectsIter = s3DownloadObjectsMap.entrySet().iterator();
while (objectsIter.hasNext()) {
Map.Entry entry = (Map.Entry) objectsIter.next();
String objectKey = (String) entry.getKey();
S3Object object = (S3Object) entry.getValue();
if (object.getContentLength() == 0 || existingFilesObjectKeys.contains(objectKey)) {
potentialClashingObjects.add(object);
}
if (existingFilesObjectKeys.contains(objectKey)) {
filesAlreadyInDownloadDirectoryMap.put(
objectKey, objectKeyToFilepathMap.get(objectKey));
}
}
if (potentialClashingObjects.size() > 0) {
// Retrieve details of potential clashes.
final S3Object[] clashingObjects = (S3Object[])
potentialClashingObjects.toArray(new S3Object[potentialClashingObjects.size()]);
(new Thread() {
@Override
public void run() {
isDownloadingObjects = true;
retrieveObjectsDetails(clashingObjects);
}
}).start();
} else {
compareRemoteAndLocalFiles(filesAlreadyInDownloadDirectoryMap, s3DownloadObjectsMap, false);
}
}
private void prepareForFilesUpload(File[] uploadFiles) {
try {
// Build map of files proposed for upload.
boolean storeEmptyDirectories = Jets3tProperties.getInstance(Constants.JETS3T_PROPERTIES_FILENAME)
.getBoolProperty("uploads.storeEmptyDirectories", true);
objectKeyToFilepathMap = FileComparer.getInstance()
.buildObjectKeyToFilepathMap(uploadFiles, "", storeEmptyDirectories);
// Build map of objects already existing in target S3 bucket with keys
// matching the proposed upload keys.
List objectsWithExistingKeys = new ArrayList();
S3Object[] existingObjects = objectTableModel.getObjects();
for (int i = 0; i < existingObjects.length; i++) {
if (objectKeyToFilepathMap.containsKey(existingObjects[i].getKey()))
{
objectsWithExistingKeys.add(existingObjects[i]);
}
}
existingObjects = (S3Object[]) objectsWithExistingKeys
.toArray(new S3Object[objectsWithExistingKeys.size()]);
s3ExistingObjectsMap = FileComparer.getInstance()
.populateObjectMap("", existingObjects);
if (existingObjects.length > 0) {
// Retrieve details of potential clashes.
final S3Object[] clashingObjects = existingObjects;
(new Thread() {
@Override
public void run() {
isUploadingFiles = true;
retrieveObjectsDetails(clashingObjects);
}
}).start();
} else {
compareRemoteAndLocalFiles(objectKeyToFilepathMap, s3ExistingObjectsMap, true);
}
} catch (Exception e) {
String message = "Unable to upload objects";
log.error(message, e);
ErrorDialog.showDialog(ownerFrame, this, cockpitLiteProperties.getProperties(), message, e);
}
}
private void compareRemoteAndLocalFiles(
final Map<String, String> objectKeyToFilepathMap, final Map s3ObjectsMap, final boolean upload)
{
final HyperlinkActivatedListener hyperlinkListener = this;
(new Thread(new Runnable() {
public void run() {
try {
// Compare objects being downloaded and existing local files.
final String statusText =
"Comparing " + s3ObjectsMap.size() + " object" + (s3ObjectsMap.size() > 1 ? "s" : "") +
" in S3 with " + objectKeyToFilepathMap.size()
+ " local file" + (objectKeyToFilepathMap.size() > 1 ? "s" : "");
startProgressDialog(statusText, "", 0, 100, null, null);
// Calculate total files size.
File[] files = objectKeyToFilepathMap.values().toArray(new File[objectKeyToFilepathMap.size()]);
final long filesSizeTotal[] = new long[1];
for (int i = 0; i < files.length; i++) {
filesSizeTotal[0] += files[i].length();
}
// Monitor generation of MD5 hash, and provide feedback via the progress bar.
BytesProgressWatcher progressWatcher = new BytesProgressWatcher(filesSizeTotal[0]) {
@Override
public void updateBytesTransferred(long byteCount) {
super.updateBytesTransferred(byteCount);
String detailsText = formatBytesProgressWatcherDetails(this, true);
int progressValue = (int)((double)getBytesTransferred() * 100 / getBytesToTransfer());
updateProgressDialog(statusText, detailsText, progressValue);
}
};
FileComparerResults comparisonResults = FileComparer.getInstance()
.buildDiscrepancyLists(objectKeyToFilepathMap, s3ObjectsMap, progressWatcher);
stopProgressDialog();
if (upload) {
performFilesUpload(comparisonResults, objectKeyToFilepathMap);
} else {
performObjectsDownload(comparisonResults, s3ObjectsMap);
}
} catch (RuntimeException e) {
stopProgressDialog();
throw e;
} catch (Exception e) {
stopProgressDialog();
String message = "Unable to " + (upload? "upload" : "download") + " objects";
log.error(message, e);
ErrorDialog.showDialog(ownerFrame, hyperlinkListener, cockpitLiteProperties.getProperties(), message, e);
}
}
})).start();
}
/**
* Retrieves details about objects including metadata etc by invoking the method
* {@link S3ServiceMulti#getObjectsHeads}.
*
* This is generally done as a prelude
* to some further action, such as displaying the objects' details or downloading the objects.
* The real action occurs in the method <code>s3ServiceEventPerformed</code> for handling
* <code>GetObjectHeadsEvent</code> events.
* @param candidateObjects
*/
private void retrieveObjectsDetails(final S3Object[] candidateObjects) {
// Identify which of the candidate objects have incomplete metadata.
ArrayList s3ObjectsIncompleteList = new ArrayList();
for (int i = 0; i < candidateObjects.length; i++) {
if (!candidateObjects[i].isMetadataComplete()) {
s3ObjectsIncompleteList.add(candidateObjects[i]);
}
}
log.debug("Of " + candidateObjects.length + " object candidates for HEAD requests "
+ s3ObjectsIncompleteList.size() + " are incomplete, performing requests for these only");
final S3Object[] incompleteObjects = (S3Object[]) s3ObjectsIncompleteList
.toArray(new S3Object[s3ObjectsIncompleteList.size()]);
(new Thread() {
@Override
public void run() {
try {
SignatureRequest[] signatureRequests = requestSignedRequests(
SignatureRequest.SIGNATURE_TYPE_HEAD, incompleteObjects);
if (signatureRequests != null) {
String[] signedRequests = new String[signatureRequests.length];
for (int i = 0; i < signedRequests.length; i++) {
signedRequests[i] = signatureRequests[i].getSignedUrl();
}
s3ServiceMulti.getObjectsHeads(signedRequests);
} else {
// Listing failed
ErrorDialog.showDialog(ownerFrame, null, cockpitLiteProperties.getProperties(),
"Sorry, you do not have the permission to view object details", null);
}
} catch (Exception e) {
stopProgressDialog();
log.error("Gatekeeper permissions check failed", e);
ErrorDialog.showDialog(ownerFrame, null, cockpitLiteProperties.getProperties(),
"Permissions check failed, please try again", e);
}
};
}).start();
}
/**
* Performs the real work of downloading files by comparing the download candidates against
* existing files, prompting the user whether to overwrite any pre-existing file versions,
* and starting {@link S3ServiceMulti#downloadObjects} where the real work is done.
*
*/
private void performObjectsDownload(FileComparerResults comparisonResults, Map s3DownloadObjectsMap) {
try {
// Determine which files to download, prompting user whether to over-write existing files
List objectKeysForDownload = new ArrayList();
objectKeysForDownload.addAll(comparisonResults.onlyOnServerKeys);
int newFiles = comparisonResults.onlyOnServerKeys.size();
int unchangedFiles = comparisonResults.alreadySynchronisedKeys.size();
int changedFiles = comparisonResults.updatedOnClientKeys.size()
+ comparisonResults.updatedOnServerKeys.size();
if (unchangedFiles > 0 || changedFiles > 0) {
// Ask user whether to replace existing unchanged and/or existing changed files.
log.debug("Files for download clash with existing local files, prompting user to choose which files to replace");
List options = new ArrayList();
String message = "Of the " + (newFiles + unchangedFiles + changedFiles)
+ " object(s) being downloaded:\n\n";
if (newFiles > 0) {
message += newFiles + " file(s) are new.\n\n";
options.add(DOWNLOAD_NEW_FILES_ONLY);
}
if (changedFiles > 0) {
message += changedFiles + " file(s) have changed.\n\n";
options.add(DOWNLOAD_NEW_AND_CHANGED_FILES);
}
if (unchangedFiles > 0) {
message += unchangedFiles + " file(s) already exist and are unchanged.\n\n";
options.add(DOWNLOAD_ALL_FILES);
}
message += "Please choose which file(s) you wish to download:";
Object response = JOptionPane.showInputDialog(
ownerFrame, message, "Replace file(s)?", JOptionPane.QUESTION_MESSAGE,
null, options.toArray(), DOWNLOAD_NEW_AND_CHANGED_FILES);
if (response == null) {
return;
}
if (DOWNLOAD_NEW_FILES_ONLY.equals(response)) {
// No change required to default objectKeysForDownload list.
} else if (DOWNLOAD_ALL_FILES.equals(response)) {
objectKeysForDownload.addAll(comparisonResults.updatedOnClientKeys);
objectKeysForDownload.addAll(comparisonResults.updatedOnServerKeys);
objectKeysForDownload.addAll(comparisonResults.alreadySynchronisedKeys);
} else if (DOWNLOAD_NEW_AND_CHANGED_FILES.equals(response)) {
objectKeysForDownload.addAll(comparisonResults.updatedOnClientKeys);
objectKeysForDownload.addAll(comparisonResults.updatedOnServerKeys);
} else {
// Download cancelled.
return;
}
}
log.debug("Downloading " + objectKeysForDownload.size() + " objects");
if (objectKeysForDownload.size() == 0) {
return;
}
// Create array of objects for download.
final S3Object[] objects = new S3Object[objectKeysForDownload.size()];
int objectIndex = 0;
for (Iterator iter = objectKeysForDownload.iterator(); iter.hasNext();) {
objects[objectIndex++] = (S3Object) s3DownloadObjectsMap.get(iter.next());
}
(new Thread() {
@Override
public void run() {
try {
SignatureRequest[] signedRequests = requestSignedRequests(
SignatureRequest.SIGNATURE_TYPE_GET, objects);
if (signedRequests != null) {
// Setup files to write to, creating parent directories when necessary.
downloadObjectsToFileMap = new HashMap();
ArrayList downloadPackageList = new ArrayList();
for (int i = 0; i < signedRequests.length; i++) {
S3Object object = signedRequests[i].buildObject();
File file = new File(downloadDirectory, object.getKey());
// Create local directories corresponding to objects flagged as dirs.
if (object.isDirectoryPlaceholder()) {
file = new File(downloadDirectory,
ObjectUtils.convertDirPlaceholderKeyNameToDirName(
objects[i].getKey()));
file.mkdirs();
}
DownloadPackage downloadPackage = ObjectUtils.createPackageForDownload(
object, file, true, false, null);
if (downloadPackage == null) {
continue;
}
downloadPackage.setSignedUrl(signedRequests[i].getSignedUrl());
downloadObjectsToFileMap.put(object.getKey(), file);
downloadPackageList.add(downloadPackage);
}
DownloadPackage[] downloadPackagesArray = (DownloadPackage[])
downloadPackageList.toArray(new DownloadPackage[downloadPackageList.size()]);
// Perform downloads.
s3ServiceMulti.downloadObjects(downloadPackagesArray);
}
} catch (Exception e) {
log.error("Download failed", e);
ErrorDialog.showDialog(ownerFrame, null, cockpitLiteProperties.getProperties(),
"Download failed, please try again", e);
}
}
}).start();
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
String message = "Unable to download objects";
log.error(message, e);
ErrorDialog.showDialog(ownerFrame, this, cockpitLiteProperties.getProperties(), message, e);
}
}
private SignatureRequest[] requestSignedRequests(String operationType, S3Object[] objects) {
try {
startProgressPanel(this, "Checking permissions", 0, null);
GatekeeperMessage responseMessage = gkClient.requestActionThroughGatekeeper(
operationType, userBucketName, objects, cockpitLiteProperties.getProperties());
stopProgressPanel(this);
String gatekeeperErrorCode = responseMessage.getApplicationProperties()
.getProperty(GatekeeperMessage.APP_PROPERTY_GATEKEEPER_ERROR_CODE);
if (gatekeeperErrorCode == null) {
// Confirm that all the signatures requested were approved
for (int i = 0; i < responseMessage.getSignatureRequests().length; i++) {
if (responseMessage.getSignatureRequests()[i].getSignedUrl() == null) {
// Some permissions missing.
return null;
}
}
return responseMessage.getSignatureRequests();
} else {
// No permissions
return null;
// ErrorDialog.showDialog(ownerFrame, null, appProperties.getProperties(),
// "Sorry, you do not have the necessary permissions", null);
}
} catch (Exception e) {
stopProgressPanel(this);
log.error("Gatekeeper permissions check failed", e);
ErrorDialog.showDialog(ownerFrame, null, cockpitLiteProperties.getProperties(),
"Permissions check failed, please try again", e);
}
return null;
}
/**
* This method is an {@link S3ServiceEventListener} action method that is invoked when this
* application's <code>S3ServiceMulti</code> triggers a <code>DownloadObjectsEvent</code>.
* <p>
* This method merely updates the progress dialog as objects are downloaded.
*
* @param event
*/
public void s3ServiceEventPerformed(DownloadObjectsEvent event) {
if (ServiceEvent.EVENT_STARTED == event.getEventCode()) {
ThreadWatcher watcher = event.getThreadWatcher();
// Show percentage of bytes transferred, if this info is available.
if (watcher.isBytesTransferredInfoAvailable()) {
startProgressPanel(event.getUniqueOperationId(),
"Download " +
byteFormatterTerse.formatByteSize(watcher.getBytesTransferred())
+ "/" + byteFormatterTerse.formatByteSize(watcher.getBytesTotal()),
100, event.getThreadWatcher().getCancelEventListener());
// ... otherwise just show the number of completed threads.
} else {
startProgressPanel(event.getUniqueOperationId(),
"Download " + event.getThreadWatcher().getCompletedThreads()
+ "/" + event.getThreadWatcher().getThreadCount(),
(int) event.getThreadWatcher().getThreadCount(),
event.getThreadWatcher().getCancelEventListener());
}
}
else if (ServiceEvent.EVENT_IN_PROGRESS == event.getEventCode()) {
ThreadWatcher watcher = event.getThreadWatcher();
// Show percentage of bytes transferred, if this info is available.
if (watcher.isBytesTransferredInfoAvailable()) {
int percentage = (int)
(((double)watcher.getBytesTransferred() / watcher.getBytesTotal()) * 100);
updateProgressPanel(event.getUniqueOperationId(),
"Download " +
byteFormatterTerse.formatByteSize(watcher.getBytesTransferred())
+ "/" + byteFormatterTerse.formatByteSize(watcher.getBytesTotal())
+ " (" +
byteFormatterTerse.formatByteSize(watcher.getBytesPerSecond()) + "/s, "
+ timeFormatterTerse.formatTime(watcher.getTimeRemaining())
+ ")",
percentage);
}
// ... otherwise just show the number of completed threads.
else {
ThreadWatcher progressStatus = event.getThreadWatcher();
String statusText = "Download " + progressStatus.getCompletedThreads()
+ " of " + progressStatus.getThreadCount() + " objects";
updateProgressPanel(event.getUniqueOperationId(), statusText, (int) progressStatus.getCompletedThreads());
}
} else if (ServiceEvent.EVENT_COMPLETED == event.getEventCode()) {
stopProgressPanel(event.getUniqueOperationId());
}
else if (ServiceEvent.EVENT_CANCELLED == event.getEventCode()) {
stopProgressPanel(event.getUniqueOperationId());
}
else if (ServiceEvent.EVENT_ERROR == event.getEventCode()) {
stopProgressPanel(event.getUniqueOperationId());
String message = "Unable to download object(s)";
log.error(message, event.getErrorCause());
ErrorDialog.showDialog(ownerFrame, this, cockpitLiteProperties.getProperties(), message, event.getErrorCause());
}
}
private void performFilesUpload(FileComparerResults comparisonResults,
Map<String, String> objectKeyToFilepathMap)
{
try {
// Determine which files to upload, prompting user whether to over-write existing files
List fileKeysForUpload = new ArrayList();
fileKeysForUpload.addAll(comparisonResults.onlyOnClientKeys);
int newFiles = comparisonResults.onlyOnClientKeys.size();
int unchangedFiles = comparisonResults.alreadySynchronisedKeys.size();
int changedFiles = comparisonResults.updatedOnClientKeys.size()
+ comparisonResults.updatedOnServerKeys.size();
if (unchangedFiles > 0 || changedFiles > 0) {
// Ask user whether to replace existing unchanged and/or existing changed files.
log.debug("Files for upload clash with existing S3 objects, prompting user to choose which files to replace");
List options = new ArrayList();
String message = "Of the " + objectKeyToFilepathMap.size()
+ " file(s) being uploaded:\n\n";
if (newFiles > 0) {
message += newFiles + " file(s) are new.\n\n";
options.add(UPLOAD_NEW_FILES_ONLY);
}
if (changedFiles > 0) {
message += changedFiles + " file(s) have changed.\n\n";
options.add(UPLOAD_NEW_AND_CHANGED_FILES);
}
if (unchangedFiles > 0) {
message += unchangedFiles + " file(s) already exist and are unchanged.\n\n";
options.add(UPLOAD_ALL_FILES);
}
message += "Please choose which file(s) you wish to upload:";
Object response = JOptionPane.showInputDialog(
ownerFrame, message, "Replace file(s)?", JOptionPane.QUESTION_MESSAGE,
null, options.toArray(), UPLOAD_NEW_AND_CHANGED_FILES);
if (response == null) {
return;
}
if (UPLOAD_NEW_FILES_ONLY.equals(response)) {
// No change required to default fileKeysForUpload list.
} else if (UPLOAD_ALL_FILES.equals(response)) {
fileKeysForUpload.addAll(comparisonResults.updatedOnClientKeys);
fileKeysForUpload.addAll(comparisonResults.updatedOnServerKeys);
fileKeysForUpload.addAll(comparisonResults.alreadySynchronisedKeys);
} else if (UPLOAD_NEW_AND_CHANGED_FILES.equals(response)) {
fileKeysForUpload.addAll(comparisonResults.updatedOnClientKeys);
fileKeysForUpload.addAll(comparisonResults.updatedOnServerKeys);
} else {
// Upload cancelled.
stopProgressDialog();
return;
}
}
if (fileKeysForUpload.size() == 0) {
return;
}
final String[] statusText = new String[1];
statusText[0] = "Prepared 0 of " + fileKeysForUpload.size() + " file(s) for upload";
startProgressDialog(statusText[0], "", 0, 100, null, null);
long bytesToProcess = 0;
for (Iterator iter = fileKeysForUpload.iterator(); iter.hasNext();) {
File file = new File(objectKeyToFilepathMap.get(iter.next().toString()));
bytesToProcess += file.length();
}
BytesProgressWatcher progressWatcher = new BytesProgressWatcher(bytesToProcess) {
@Override
public void updateBytesTransferred(long byteCount) {
super.updateBytesTransferred(byteCount);
String detailsText = formatBytesProgressWatcherDetails(this, false);
int progressValue = (int)((double)getBytesTransferred() * 100 / getBytesToTransfer());
updateProgressDialog(statusText[0], detailsText, progressValue);
}
};
// Populate S3Objects representing upload files with metadata etc.
final S3Object[] objects = new S3Object[fileKeysForUpload.size()];
int objectIndex = 0;
for (Iterator iter = fileKeysForUpload.iterator(); iter.hasNext();) {
String fileKey = iter.next().toString();
File file = new File(objectKeyToFilepathMap.get(fileKey));
S3Object newObject = ObjectUtils
.createObjectForUpload(fileKey, file, null, false, progressWatcher);
statusText[0] = "Prepared " + (objectIndex + 1)
+ " of " + fileKeysForUpload.size() + " file(s) for upload";
objects[objectIndex++] = newObject;
}
stopProgressDialog();
// Confirm we have permission to do this.
SignatureRequest[] signedRequests = requestSignedRequests(
SignatureRequest.SIGNATURE_TYPE_PUT, objects);
if (signedRequests != null) {
// Upload the files.
SignedUrlAndObject[] urlAndObjs = new SignedUrlAndObject[signedRequests.length];
for (int i = 0; i < signedRequests.length; i++) {
urlAndObjs[i] = new SignedUrlAndObject(
signedRequests[i].getSignedUrl(), objects[i]);
}
s3ServiceMulti.putObjects(urlAndObjs);
}
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
stopProgressDialog();
String message = "Unable to upload object(s)";
log.error(message, e);
ErrorDialog.showDialog(ownerFrame, this, cockpitLiteProperties.getProperties(), message, e);
}
}
/**
* This method is an {@link S3ServiceEventListener} action method that is invoked when this
* application's <code>S3ServiceMulti</code> triggers a <code>CreateObjectsEvent</code>.
* <p>
* This method merely updates the progress dialog as files are uploaded.
*
* @param event
*/
public void s3ServiceEventPerformed(final CreateObjectsEvent event) {
if (ServiceEvent.EVENT_STARTED == event.getEventCode()) {
ThreadWatcher watcher = event.getThreadWatcher();
// Show percentage of bytes transferred, if this info is available.
if (watcher.isBytesTransferredInfoAvailable()) {
startProgressPanel(event.getUniqueOperationId(),
"Upload " +
byteFormatterTerse.formatByteSize(watcher.getBytesTransferred())
+ "/" + byteFormatterTerse.formatByteSize(watcher.getBytesTotal()),
100, event.getThreadWatcher().getCancelEventListener());
}
// ... otherwise show the number of completed threads.
else {
startProgressPanel(event.getUniqueOperationId(),
"Upload 0/" + watcher.getThreadCount(),
(int) watcher.getThreadCount(),
event.getThreadWatcher().getCancelEventListener());
}
}
else if (ServiceEvent.EVENT_IN_PROGRESS == event.getEventCode()) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
for (int i = 0; i < event.getCreatedObjects().length; i++) {
objectTableModel.addObject(event.getCreatedObjects()[i]);
}
}
});
ThreadWatcher watcher = event.getThreadWatcher();
// Show percentage of bytes transferred, if this info is available.
if (watcher.isBytesTransferredInfoAvailable()) {
if (watcher.getBytesTransferred() >= watcher.getBytesTotal()) {
// Upload is completed, just waiting on resonse from S3.
updateProgressPanel(event.getUniqueOperationId(), "Confirming", 100);
} else {
int percentage = (int)
(((double)watcher.getBytesTransferred() / watcher.getBytesTotal()) * 100);
updateProgressPanel(event.getUniqueOperationId(),
"Upload " +
byteFormatterTerse.formatByteSize(watcher.getBytesTransferred())
+ "/" + byteFormatterTerse.formatByteSize(watcher.getBytesTotal())
+ " (" +
byteFormatterTerse.formatByteSize(watcher.getBytesPerSecond()) + "/s, "
+ timeFormatterTerse.formatTime(watcher.getTimeRemaining())
+ ")", percentage);
}
}
// ... otherwise show the number of completed threads.
else {
ThreadWatcher progressStatus = event.getThreadWatcher();
updateProgressPanel(event.getUniqueOperationId(),
"Upload " + progressStatus.getCompletedThreads()
+ "/" + progressStatus.getThreadCount(),
(int) progressStatus.getCompletedThreads());
}
}
else if (ServiceEvent.EVENT_COMPLETED == event.getEventCode()) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
updateObjectsSummary();
}
});
stopProgressPanel(event.getUniqueOperationId());
}
else if (ServiceEvent.EVENT_CANCELLED == event.getEventCode()) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
updateObjectsSummary();
}
});
stopProgressPanel(event.getUniqueOperationId());
}
else if (ServiceEvent.EVENT_ERROR == event.getEventCode()) {
stopProgressPanel(event.getUniqueOperationId());
String message = "Unable to upload object(s)";
log.error(message, event.getErrorCause());
ErrorDialog.showDialog(ownerFrame, this, cockpitLiteProperties.getProperties(), message, event.getErrorCause());
}
}
private void generatePublicGetUrl() {
final S3Object[] objects = getSelectedObjects();
if (objects.length != 1) {
log.warn("Ignoring Generate Public URL object command, can only operate on a single object");
return;
}
S3Object currentObject = objects[0];
try {
String hostAndBucket = null;
if (userVanityHost != null) {
hostAndBucket = userVanityHost;
} else {
boolean disableDnsBuckets = false;
String s3Endpoint = Jets3tProperties.getInstance(Constants.JETS3T_PROPERTIES_FILENAME)
.getStringProperty("s3service.s3-endpoint", Constants.S3_DEFAULT_HOSTNAME);
hostAndBucket = ServiceUtils.generateS3HostnameForBucket(
userBucketName, disableDnsBuckets, s3Endpoint);
if (!ServiceUtils.isBucketNameValidDNSName(userBucketName)) {
// If bucket name isn't DNS compatible, we must include the bucket
// name as a URL path item.
hostAndBucket += "/" + userBucketName;
}
}
String url = "http://" + hostAndBucket + "/" + userPath + currentObject.getKey();
// Display signed URL
String dialogText = "Public URL for '" + currentObject.getKey() + "'.";
// Ensure dialog text is at least 150 characters (to force dialog to be wider)
if (dialogText.length() < 150) {
int charsShort = 150 - dialogText.length();
StringBuffer padding = new StringBuffer();
for (int i = 0; i < charsShort / 2; i++) {
padding.append(" ");
}
dialogText = padding.toString() + dialogText + padding.toString();
}
JOptionPane.showInputDialog(ownerFrame, dialogText,
"URL", JOptionPane.INFORMATION_MESSAGE, null, null, url);
} catch (NumberFormatException e) {
String message = "Hours must be a valid decimal value; eg 3, 0.1";
log.error(message, e);
ErrorDialog.showDialog(ownerFrame, this, cockpitLiteProperties.getProperties(), message, e);
} catch (Exception e) {
String message = "Unable to generate public GET URL";
log.error(message, e);
ErrorDialog.showDialog(ownerFrame, this, cockpitLiteProperties.getProperties(), message, e);
}
}
private void deleteSelectedObjects() {
final S3Object[] objects = getSelectedObjects();
if (objects.length == 0) {
log.warn("Ignoring delete object(s) command, no currently selected objects");
return;
}
int response = JOptionPane.showConfirmDialog(ownerFrame,
(objects.length == 1 ?
"Are you sure you want to delete '" + objects[0].getKey() + "'?" :
"Are you sure you want to delete " + objects.length + " object(s)"
),
"Delete Object(s)?", JOptionPane.YES_NO_OPTION);
if (response == JOptionPane.NO_OPTION) {
return;
}
new Thread() {
@Override
public void run() {
try {
SignatureRequest[] signatureRequests = requestSignedRequests(
SignatureRequest.SIGNATURE_TYPE_DELETE, objects);
if (signatureRequests != null) {
String[] signedRequests = new String[signatureRequests.length];
for (int i = 0; i < signedRequests.length; i++) {
signedRequests[i] = signatureRequests[i].getSignedUrl();
}
s3ServiceMulti.deleteObjects(signedRequests);
} else {
ErrorDialog.showDialog(ownerFrame, null, cockpitLiteProperties.getProperties(),
"Sorry, you do not have the permission to delete files", null);
}
} catch (Exception e) {
stopProgressDialog();
log.error("Gatekeeper permissions check failed", e);
ErrorDialog.showDialog(ownerFrame, null, cockpitLiteProperties.getProperties(),
"Permissions check failed, please try again", e);
}
}
}.start();
}
/**
* This method is an {@link S3ServiceEventListener} action method that is invoked when this
* application's <code>S3ServiceMulti</code> triggers a <code>DeleteObjectsEvent</code>.
* <p>
* This method merely updates the progress dialog as objects are deleted.
*
* @param event
*/
public void s3ServiceEventPerformed(final DeleteObjectsEvent event) {
if (ServiceEvent.EVENT_STARTED == event.getEventCode()) {
startProgressDialog(
"Deleted 0 of " + event.getThreadWatcher().getThreadCount() + " object(s)",
"", 0, (int) event.getThreadWatcher().getThreadCount(), "Cancel Delete Objects",
event.getThreadWatcher().getCancelEventListener());
}
else if (ServiceEvent.EVENT_IN_PROGRESS == event.getEventCode()) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
for (int i = 0; i < event.getDeletedObjects().length; i++) {
objectTableModel.removeObject(
event.getDeletedObjects()[i]);
}
}
});
ThreadWatcher progressStatus = event.getThreadWatcher();
String statusText = "Deleted " + progressStatus.getCompletedThreads()
+ " of " + progressStatus.getThreadCount() + " object(s)";
updateProgressDialog(statusText, "", (int) progressStatus.getCompletedThreads());
}
else if (ServiceEvent.EVENT_COMPLETED == event.getEventCode()) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
updateObjectsSummary();
}
});
stopProgressDialog();
}
else if (ServiceEvent.EVENT_CANCELLED == event.getEventCode()) {
stopProgressDialog();
}
else if (ServiceEvent.EVENT_ERROR == event.getEventCode()) {
stopProgressDialog();
String message = "Unable to delete object(s)";
log.error(message, event.getErrorCause());
ErrorDialog.showDialog(ownerFrame, this, cockpitLiteProperties.getProperties(), message, event.getErrorCause());
}
}
/**
* This method is an {@link S3ServiceEventListener} action method that is invoked when this
* application's <code>S3ServiceMulti</code> triggers a <code>GetObjectHeadsEvent</code>.
* <p>
* This method merely updates the progress dialog as object details (heads) are retrieved.
*
* @param event
*/
public void s3ServiceEventPerformed(final GetObjectHeadsEvent event) {
if (ServiceEvent.EVENT_STARTED == event.getEventCode()) {
if (event.getThreadWatcher().getThreadCount() > 0) {
startProgressDialog("Retrieved details for 0 of "
+ event.getThreadWatcher().getThreadCount() + " object(s)",
"", 0, (int) event.getThreadWatcher().getThreadCount(), "Cancel Retrieval",
event.getThreadWatcher().getCancelEventListener());
}
}
else if (ServiceEvent.EVENT_IN_PROGRESS == event.getEventCode()) {
final ThreadWatcher progressStatus = event.getThreadWatcher();
// Store detail-complete objects in table.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
synchronized (lock) {
// Retain selected status of objects for downloads or properties
for (int i = 0; i < event.getCompletedObjects().length; i++) {
S3Object object = event.getCompletedObjects()[i];
int modelIndex = objectTableModel.addObject(object);
log.debug("Updated table with " + object.getKey() + ", content-type=" + object.getContentType());
if (isDownloadingObjects) {
s3DownloadObjectsMap.put(object.getKey(), object);
log.debug("Updated object download list with " + object.getKey()
+ ", content-type=" + object.getContentType());
} else if (isUploadingFiles) {
s3ExistingObjectsMap.put(object.getKey(), object);
log.debug("Updated object upload list with " + object.getKey()
+ ", content-type=" + object.getContentType());
}
int viewIndex = objectTableModelSorter.viewIndex(modelIndex);
if (isDownloadingObjects || isViewingObjectProperties) {
objectsTable.addRowSelectionInterval(viewIndex, viewIndex);
}
}
}
}
});
// Update progress of GetObject requests.
String statusText = "Retrieved details for " + progressStatus.getCompletedThreads()
+ " of " + progressStatus.getThreadCount() + " object(s)";
updateProgressDialog(statusText, "", (int) progressStatus.getCompletedThreads());
}
else if (ServiceEvent.EVENT_COMPLETED == event.getEventCode()) {
// Stop GetObjectHead progress display.
stopProgressDialog();
synchronized (lock) {
if (isDownloadingObjects) {
compareRemoteAndLocalFiles(filesAlreadyInDownloadDirectoryMap, s3DownloadObjectsMap, false);
isDownloadingObjects = false;
} else if (isUploadingFiles) {
compareRemoteAndLocalFiles(objectKeyToFilepathMap, s3ExistingObjectsMap, true);
isUploadingFiles = false;
} else if (isViewingObjectProperties) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
ItemPropertiesDialog.showDialog(ownerFrame, getSelectedObjects(),
cockpitLiteProperties.getProperties(),
// Only admin users with all rights can view metadata
(userCanUpload && userCanDownload && userCanACL && userCanDelete));
isViewingObjectProperties = false;
}
});
}
}
}
else if (ServiceEvent.EVENT_CANCELLED == event.getEventCode()) {
stopProgressDialog();
}
else if (ServiceEvent.EVENT_ERROR == event.getEventCode()) {
stopProgressDialog();
String message = "Unable to retrieve object(s) details";
log.error(message, event.getErrorCause());
ErrorDialog.showDialog(ownerFrame, this, cockpitLiteProperties.getProperties(), message, event.getErrorCause());
}
}
private String formatTransferDetails(ThreadWatcher watcher) {
long bytesPerSecond = watcher.getBytesPerSecond();
String detailsText = byteFormatter.formatByteSize(bytesPerSecond) + "/s";
if (watcher.isTimeRemainingAvailable()) {
long secondsRemaining = watcher.getTimeRemaining();
detailsText += " - Time remaining: " + timeFormatterTerse.formatTime(secondsRemaining);
}
return detailsText;
}
private String formatBytesProgressWatcherDetails(BytesProgressWatcher watcher, boolean includeBytes) {
long secondsRemaining = watcher.getRemainingTime();
String detailsText =
(includeBytes
? byteFormatter.formatByteSize(watcher.getBytesTransferred())
+ " of " + byteFormatter.formatByteSize(watcher.getBytesToTransfer())
+ " - "
: "")
+ "Time remaining: " +
timeFormatterTerse.formatTime(secondsRemaining);
return detailsText;
}
/**
* Follows hyperlinks clicked on by a user. This is achieved differently depending on whether
* Cockpit is running as an applet or as a stand-alone application:
* <ul>
* <li>Application: Detects the default browser application for the user's system (using
* <tt>BareBonesBrowserLaunch</tt>) and opens the link as a new window in that browser</li>
* <li>Applet: Opens the link in the current browser using the applet's context</li>
* </ul>
*
* @param url
* the url to open
* @param target
* the target pane to open the url in, eg "_blank". This may be null.
*/
public void followHyperlink(URL url, String target) {
if (!isStandAloneApplication) {
if (target == null) {
getAppletContext().showDocument(url);
} else {
getAppletContext().showDocument(url, target);
}
} else {
BareBonesBrowserLaunch.openURL(url.toString());
}
}
public void setCredentials(AuthScope authscope, Credentials credentials) {
mCredentialProvider.setCredentials(authscope, credentials);
}
/**
* Clear credentials.
*/
public void clear() {
mCredentialProvider.clear();
}
/**
* Implementation method for the CredentialsProvider interface.
* <p>
* Based on sample code:
* <a href="http://svn.apache.org/viewvc/jakarta/commons/proper/httpclient/trunk/src/examples/InteractiveAuthenticationExample.java?view=markup">InteractiveAuthenticationExample</a>
*
*/
public Credentials getCredentials(AuthScope scope) {
if (scope == null || scope.getScheme() == null) {
return null;
}
Credentials credentials = mCredentialProvider.getCredentials(scope);
if (credentials!=null){
return credentials;
}
try {
if (scope.getScheme().equals("ntlm")) {
AuthenticationDialog pwDialog = new AuthenticationDialog(
ownerFrame, "Authentication Required",
"<html>Host <b>" + scope.getHost() + ":" + scope.getPort()
+ "</b> requires Windows authentication</html>", true);
pwDialog.setVisible(true);
if (pwDialog.getUser().length() > 0) {
credentials = new NTCredentials(
pwDialog.getUser(),
pwDialog.getPassword(),
scope.getHost(),
pwDialog.getDomain());
}
pwDialog.dispose();
} else if (scope.getScheme().equals("basic")
|| scope.getScheme().equals("digest")) {
//authscheme instanceof RFC2617Scheme
AuthenticationDialog pwDialog = new AuthenticationDialog(
ownerFrame, "Authentication Required",
"<html><center>Host <b>" + scope.getHost() + ":" + scope.getPort() + "</b>"
+ " requires authentication for the realm:<br><b>" + scope.getRealm()
+ "</b></center></html>", false);
pwDialog.setVisible(true);
if (pwDialog.getUser().length() > 0) {
credentials = new UsernamePasswordCredentials(pwDialog.getUser(), pwDialog.getPassword());
}
pwDialog.dispose();
} else {
throw new InvalidCredentialsException(
"Unsupported authentication scheme: " + scope.getScheme());
}
if (credentials != null){
mCredentialProvider.setCredentials(scope, credentials);
}
return credentials;
} catch (Exception e) {
throw new IllegalArgumentException(e.getMessage(), e);
}
}
private boolean isObjectFilteringActive() {
if (!filterObjectsCheckBox.isSelected()) {
return false;
} else {
if (filterObjectsPrefix.getText().length() > 0) {
return true;
} else {
return false;
}
}
}
public void s3ServiceEventPerformed(CreateBucketsEvent event) {
// Not applicable in this app.
}
public void s3ServiceEventPerformed(CopyObjectsEvent event) {
// Not applicable in this app.
}
public static String getAclDescription(AccessControlList acl) {
if (acl == null) {
return ACL_UNKNOWN_DESCRIPTION;
}
for (GrantAndPermission gap: acl.getGrantAndPermissions()) {
if (GroupGrantee.ALL_USERS.equals(gap.getGrantee())
&& Permission.PERMISSION_READ.equals(gap.getPermission()))
{
return ACL_PUBLIC_DESCRIPTION;
}
}
if (AccessControlList.REST_CANNED_PUBLIC_READ.equals(acl)) {
return ACL_PUBLIC_DESCRIPTION;
}
return ACL_PRIVATE_DESCRIPTION;
}
private class ContextMenuListener extends MouseAdapter {
@Override
public void mousePressed(MouseEvent e) {
showContextMenu(e);
}
@Override
public void mouseReleased(MouseEvent e) {
showContextMenu(e);
}
private void showContextMenu(MouseEvent e) {
if (e.isPopupTrigger()) {
// Select item under context-click.
if (e.getSource() instanceof JList) {
JList jList = (JList) e.getSource();
int locIndex = jList.locationToIndex(e.getPoint());
if (locIndex >= 0) {
jList.setSelectedIndex(locIndex);
}
} else if (e.getSource() instanceof JTable) {
JTable jTable = (JTable) e.getSource();
int rowIndex = jTable.rowAtPoint(e.getPoint());
if (rowIndex >= 0) {
jTable.addRowSelectionInterval(rowIndex, rowIndex);
}
}
// Show context popup menu.
if (e.getSource().equals(objectsTable)) {
showObjectPopupMenu((JComponent)e.getSource(), e.getX(), e.getY());
}
}
}
}
/**
* Runs Cockpit as a stand-alone application.
* @param args
* @throws Exception
*/
public static void main(String args[]) throws Exception {
JFrame ownerFrame = new JFrame("JetS3t Cockpit-Lite");
ownerFrame.addWindowListener(new WindowListener() {
public void windowOpened(WindowEvent e) {
}
public void windowClosing(WindowEvent e) {
e.getWindow().dispose();
}
public void windowClosed(WindowEvent e) {
}
public void windowIconified(WindowEvent e) {
}
public void windowDeiconified(WindowEvent e) {
}
public void windowActivated(WindowEvent e) {
}
public void windowDeactivated(WindowEvent e) {
}
});
// Read arguments as properties of the form: <propertyName>'='<propertyValue>
Properties argumentProperties = new Properties();
if (args.length > 0) {
for (int i = 0; i < args.length; i++) {
String arg = args[i];
int delimIndex = arg.indexOf("=");
if (delimIndex >= 0) {
String name = arg.substring(0, delimIndex);
String value = arg.substring(delimIndex + 1);
argumentProperties.put(name, value);
} else {
System.out.println("Ignoring property argument with incorrect format: " + arg);
}
}
}
new CockpitLite(ownerFrame, argumentProperties);
}
}