/**
* Copyright (c) 2008, Aberystwyth University
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above
* copyright notice, this list of conditions and the
* following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* - Neither the name of the Centre for Advanced Software and
* Intelligent Systems (CASIS) nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
package org.purl.sword.client;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.JToolBar;
import javax.swing.KeyStroke;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import org.jdesktop.swingworker.SwingWorker;
import org.purl.sword.base.DepositResponse;
import org.purl.sword.base.ServiceDocument;
import org.purl.sword.base.SWORDException;
import org.purl.sword.base.SWORDErrorDocument;
import org.purl.sword.base.SwordValidationInfo;
import org.purl.sword.base.SwordValidationInfoType;
/**
* Main class that creates the GUI interface for the demonstration SWORD client.
*
* @author Neil Taylor
* @author Jim Downing (enhancements so the GUI will work entirely from a single
* jar)
*/
public class GuiClient extends JFrame implements ClientType,
ServiceSelectedListener {
/**
* The property file that contains the main properties that can be used to
* configure the application.
*/
private final static String PROPERTY_FILE = "SwordClient.properties";
/**
* Label for the onBehalfOf property file label.
*/
private final static String ON_BEHALF_OF = "onBehalfOf";
/**
* The dialog to get details for the service location.
*/
private ServiceDialog serviceDialog;
/**
* The post dialog. This is used to determine which file should be posted.
*/
private PostDialog postDialog;
/**
* The main panel that holds the service details and the message panel.
*/
private MainPanel mainPanel = null;
/**
* The action that posts data to a remote collection.
*/
private PostAction postAction = null;
/**
* The debug menu item.
*/
JCheckBoxMenuItem debug = null;
/**
* Action that processes requests to access service documents.
*/
Action serviceAction = null;
/**
* List of properties.
*/
Properties props = null;
/**
* The connection to the client.
*/
private Client swordclient;
/**
* The logger.
*/
private static Logger log = Logger.getLogger(GuiClient.class);
private File propFile;
/**
* Create a new instance of the tool.
*/
public GuiClient() {
super("SWORD Demonstration Client");
}
/**
* Load the properties from a file.
*/
private void loadProperties() {
log.debug("Loading props");
InputStream stream = null;
try {
props = new Properties();
URL propUrl =
Thread.currentThread().getContextClassLoader().getResource(PROPERTY_FILE);
log.debug("The property file url is: " + propUrl);
if (propUrl == null) {
throw new IOException("Could not find properties file.");
}
if ("file".equals(propUrl.getProtocol())) {
propFile = new File(propUrl.toURI());
} else {
propFile = new File(PROPERTY_FILE);
FileUtils.copyURLToFile(propUrl, propFile);
}
stream = new FileInputStream(propFile);
props.load(stream);
} catch (IOException ioe) {
log.error("Unable to load property file");
JOptionPane.showMessageDialog(GuiClient.this,
"Unable to load properties file " + ioe.getMessage(),
"Properties", JOptionPane.ERROR_MESSAGE);
} catch (URISyntaxException e) {
throw new RuntimeException(
"Most unexpectedly, a file URL is not a URI.", e);
} finally {
IOUtils.closeQuietly(stream);
}
log.info("Loaded props");
}
/**
* Process the core properties that will affect the client. This will
* currently set the proxyHost value, if it is set.
*/
private void processProperties() {
if (props != null) {
String value = props.getProperty("proxyHost");
log.debug("the proxy host is set to: " + value);
if (value != null && value.trim().length() > 0) {
try {
URL url = new URL(value);
int port = url.getPort();
if (port == -1) {
port = 80;
}
log.debug("host is : " + url.getHost());
swordclient.setProxy(url.getHost(), port);
} catch (MalformedURLException mue) {
JOptionPane.showMessageDialog(GuiClient.this,
"Unable to set Proxy Host " + mue.getMessage(),
"Properties", JOptionPane.ERROR_MESSAGE);
}
} else {
swordclient.clearProxy();
}
}
}
/**
* Run the client. This is the main entry point into this GUI client. The
* client should process the options and start running.
*
* @param options
* The list of options extracted from the command line.
*/
public void run(ClientOptions options) {
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
saveProperties();
log.debug("Exiting.");
System.exit(0);
}
});
// add the menus
JMenuBar menubar = new JMenuBar();
Action quitAction = new QuitAction();
serviceAction = new AddServiceAction();
postAction = new PostAction();
JMenu fileMenu = new JMenu("File");
fileMenu.add(serviceAction);
fileMenu.add(postAction);
fileMenu.addSeparator();
fileMenu.add(quitAction);
menubar.add(fileMenu);
JMenu optionsMenu = new JMenu("Options");
debug = new JCheckBoxMenuItem(new DebugAction());
optionsMenu.add(debug);
optionsMenu.add(new EditPropertiesAction());
optionsMenu.add(new ValidationInfoAction());
menubar.add(optionsMenu);
JMenu actionMenu = new JMenu("Help");
actionMenu.add(new HelpAction());
actionMenu.add(new AboutAction());
menubar.add(actionMenu);
setJMenuBar(menubar);
JToolBar toolbar = new JToolBar();
toolbar.setFloatable(false);
add(toolbar, BorderLayout.PAGE_START);
toolbar.add(serviceAction);
toolbar.add(postAction);
Container c = getContentPane();
log.debug("Creating main panel ...");
mainPanel = new MainPanel(options.isNoCapture());
c.add(mainPanel);
log.debug("Initialising client ...");
swordclient = new Client();
log.debug("Loading props ...");
loadProperties();
processProperties();
pack();
setVisible(true);
}
/**
* Save the properties to a file.
*/
private void saveProperties() {
// if the properties is not null then save it to file
if (props != null && propFile != null) {
OutputStream out = null;
try {
out = new FileOutputStream(propFile);
log.debug("saving to... " + propFile);
props.store(out, null);
} catch (FileNotFoundException e) {
log.error("Unable to store the file: " + e.getMessage(), e);
} catch (IOException e) {
log.error("Error storing the file: " + e.getMessage(), e);
} finally {
IOUtils.closeQuietly(out);
}
} else {
log.warn("Either props: " + props + " or prop file: " + propFile
+ " were null - not saving.");
}
}
/**
* Set the enabled status for the service and post actions.
*
* @param enabled
* The status.
*/
private void enableActions(boolean enabled) {
serviceAction.setEnabled(enabled);
postAction.setEnabled(enabled);
}
/**
* Initialise the server connection information. If there is a username and
* password, the basic credentials will also be set. Otherwise, the
* credentials will be cleared.
*
* @param location
* The location to connect to. This is a URL, of the format,
* http://a.host.com:port/. The host name and port number will be
* extracted. If the port is not specified, a default port of 80
* will be used.
* @param username
* The username. If this is null or an empty string, the basic
* credentials will be cleared.
* @param password
* The password. If this is null or an empty string, the basic
* credentials will be cleared.
*
* @throws MalformedURLException
* if there is an error processing the URL.
*/
private void initialiseServer(String location, String username,
String password) throws MalformedURLException {
URL url = new URL(location);
int port = url.getPort();
if (port == -1) {
port = 80;
}
swordclient.setServer(url.getHost(), port);
if (username != null && username.length() > 0 && password != null
&& password.length() > 0) {
swordclient.setCredentials(username, password);
} else {
swordclient.clearCredentials();
}
swordclient.setUserAgent(ClientConstants.SERVICE_NAME);
}
/***************************************************************************
*
* Actions
*
**************************************************************************/
/**
* Action to quit the application.
*
* @author Neil Taylor
*/
protected class QuitAction extends AbstractAction {
/**
* Create the Quit action.
*/
public QuitAction() {
super("Quit");
this.putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(
KeyEvent.VK_Q, java.awt.Toolkit.getDefaultToolkit()
.getMenuShortcutKeyMask()));
}
/**
* Exit the application. This method does not confirm the exit.
*
* @param event
* The event information for this action.
*/
public void actionPerformed(ActionEvent event) {
saveProperties();
System.exit(0);
}
}
/**
* Action to quit the application.
*
* @author Neil Taylor
*/
protected class EditPropertiesAction extends AbstractAction {
/**
* Create the Edit Properties action.
*/
public EditPropertiesAction() {
super("Edit Properties");
}
/**
* Exit the application. This method does not confirm the exit.
*
* @param event
* The event information for this action.
*/
public void actionPerformed(ActionEvent event) {
PropertiesDialog dialog = new PropertiesDialog(GuiClient.this,
props);
dialog.show();
processProperties();
}
}
/**
* Action to access a service document and process the results.
*
* @author Neil Taylor
*/
protected class AddServiceAction extends AbstractAction {
/**
* Create a new instance.
*/
public AddServiceAction() {
super("Add Service");
ClassLoader loader = this.getClass().getClassLoader();
URL s = loader.getResource("images/AddServiceButton.gif");
Icon icon = new ImageIcon(s);
putValue(Action.SMALL_ICON, icon);
putValue(Action.SHORT_DESCRIPTION, "Add Service");
}
/**
* Start the process to access the service document. This launches the
* dialog and then starts a worker thread to access the remote service
* document.
*/
public void actionPerformed(ActionEvent event) {
initialiseServiceDialog();
int result = serviceDialog.show();
if (result != JOptionPane.OK_OPTION) {
return;
}
final String location = serviceDialog.getLocation();
if (location == null || location.length() == 0) {
JOptionPane.showMessageDialog(GuiClient.this,
"You did not specify a URL", "Service Access Error",
JOptionPane.ERROR_MESSAGE);
return;
}
// create the worker process that will run the process.
SwingWorker<String, String> worker = new SwingWorker<String, String>() {
/**
* Run the thread.
*/
@Override
protected String doInBackground() throws Exception {
try {
enableActions(false);
setCursor(new Cursor(Cursor.WAIT_CURSOR));
String username = serviceDialog.getUsername();
String password = serviceDialog.getPassword();
initialiseServer(location, username, password);
publish("Requesting the document from " + location);
ServiceDocument document = swordclient
.getServiceDocument(location, serviceDialog
.getOnBehalfOf());
publish("Got the document");
Status status = swordclient.getStatus();
publish("The status is: " + status);
SwordValidationInfo info = swordclient.getLastUnmarshallInfo();
if( info != null && info.getType() == SwordValidationInfoType.VALID)
{
publish("The document was valid");
}
else if( info != null )
{
publish("This document did not validate.");
StringBuffer buffer = new StringBuffer();
info.createString(info, buffer, " ");
publish(buffer.toString());
}
if (status.getCode() == 200) {
mainPanel.processServiceDocument(location, document);
mainPanel.addMessage(document.marshall());
publish("Data received for location: " + location);
} else {
JOptionPane.showMessageDialog(GuiClient.this,
"Unable to access resource. Status is: "
+ status.toString(),
"Service Access",
JOptionPane.WARNING_MESSAGE);
}
} catch (MalformedURLException ex) {
JOptionPane.showMessageDialog(GuiClient.this,
"There is an error with the URL. "
+ ex.getMessage(),
"Service Access Error",
JOptionPane.ERROR_MESSAGE);
ex.printStackTrace();
} catch (SWORDClientException sce) {
JOptionPane.showMessageDialog(GuiClient.this,
"There was an error accessing the resource. "
+ sce.getMessage(),
"Service Access Error",
JOptionPane.ERROR_MESSAGE);
sce.printStackTrace();
}
return "Finished";
}
/**
* Called when the worker thread is complete.
*/
@Override
protected void done() {
enableActions(true);
setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
}
/**
* Process the output from the worker thread.
*
* @param chunks
* The list of output to show.
*/
@Override
protected void process(List<String> chunks) {
for (String row : chunks) {
String message = "status: " + row;
mainPanel.addMessage(message);
mainPanel.setStatus(message);
}
}
};
worker.execute();
}
/**
* Initialise the service dialog.
*/
private void initialiseServiceDialog() {
if (serviceDialog == null) {
serviceDialog = new ServiceDialog(GuiClient.this);
}
String value = props.getProperty("serviceurls");
if (value != null) {
String[] services = value.split(",");
serviceDialog.addServiceUrls(services);
}
value = props.getProperty("users");
if (value != null) {
String[] users = value.split(",");
serviceDialog.addUserIds(users);
}
value = props.getProperty(ON_BEHALF_OF);
if (value != null) {
String[] users = value.split(",");
serviceDialog.addOnBehalfOf(users);
}
}
}
/**
* Action to process the toggle to show and hide the debug panel.
*
* @author Neil Taylor
*/
protected class ValidationInfoAction extends AbstractAction {
/**
* Create a new instance.
*/
public ValidationInfoAction() {
super("Show Last Validation Info");
}
/**
* Handle the action. Update the debug status, based on the value in the
* debug menu item.
*
* @param event
* The event.
*/
public void actionPerformed(ActionEvent event) {
JOptionPane.showOptionDialog(GuiClient.this,
createPanel(),
"View Validation Info",
JOptionPane.OK_OPTION,
JOptionPane.INFORMATION_MESSAGE,
null, new String[] { "OK" }, "OK");
}
private JPanel createPanel()
{
JPanel panel = new JPanel();
JTextArea text = new JTextArea();
//text.setAutoscrolls(true);
JScrollPane areaScrollPane = new JScrollPane(text);
areaScrollPane.setVerticalScrollBarPolicy(
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
areaScrollPane.setPreferredSize(new Dimension(500, 400));
if( swordclient != null )
{
SwordValidationInfo info = swordclient.getLastUnmarshallInfo();
if( info != null )
{
StringBuffer buffer = new StringBuffer();
info.createString(info, buffer, "");
text.setText(buffer.toString());
}
else
{
text.setText("There is no validaiton information to display");
}
}
else
{
text.setText("There is no validaiton information to display");
}
panel.add(areaScrollPane, BorderLayout.CENTER);
panel.setSize(500, 400);
return panel;
}
}
/**
* Action to process the toggle to show and hide the debug panel.
*
* @author Neil Taylor
*/
protected class DebugAction extends AbstractAction {
/**
* Create a new instance.
*/
public DebugAction() {
super("Show Debug Panel");
}
/**
* Handle the action. Update the debug status, based on the value in the
* debug menu item.
*
* @param event
* The event.
*/
public void actionPerformed(ActionEvent event) {
boolean setDebug = debug.isSelected();
mainPanel.showDebugTab(setDebug);
}
}
/**
* Controlling action to post a file to the server.
*
* @author Neil Taylor
*/
protected class PostAction extends AbstractAction {
/**
* The collection location to post to.
*/
private String collection = null;
/**
* Create a post action.
*/
public PostAction() {
super("Post");
ClassLoader loader = this.getClass().getClassLoader();
URL url = loader.getResource("images/PostButton.gif");
Icon icon = new ImageIcon(url);
putValue(Action.SMALL_ICON, icon);
putValue(Action.SHORT_DESCRIPTION, "Post file");
}
/**
* Display the post dialog to run the post process.
*
* @param event
* The event.
*/
public void actionPerformed(ActionEvent event) {
initialisePostDialog();
int result = postDialog.show();
if (result == JOptionPane.OK_OPTION) {
// create the worker process that will run the process.
SwingWorker<String, String> worker = new SwingWorker<String, String>() {
/**
* Run the thread.
*/
@Override
protected String doInBackground() throws Exception {
enableActions(false);
setCursor(new Cursor(Cursor.WAIT_CURSOR));
PostDestination[] destinations = postDialog
.getDestinations();
String location;
String username;
String password;
for (PostDestination destination : destinations) {
try {
location = destination.getUrl();
username = destination.getUsername();
password = destination.getPassword();
initialiseServer(location, username, password);
if (username != null && username.length() > 0
&& password != null
&& password.length() > 0) {
publish("Setting the username/password: "
+ username + " " + password);
swordclient.setCredentials(username,
password);
} else {
swordclient.clearCredentials();
}
PostMessage message = new PostMessage();
message.setDestination(location);
message.setFilepath(postDialog.getFile());
message.setFiletype(postDialog.getFileType());
message.setFormatNamespace(postDialog
.getFormatNamespace());
message.setUseMD5(postDialog.useMd5());
message.setVerbose(postDialog.useVerbose());
message.setOnBehalfOf(destination
.getOnBehalfOf());
message.setNoOp(postDialog.useNoOp());
message.setChecksumError(postDialog
.corruptMD5());
message.setCorruptRequest(postDialog
.corruptRequest());
message.setUserAgent(ClientConstants.SERVICE_NAME);
publish("Posting file to: " + location);
DepositResponse document = swordclient
.postFile(message);
Status status = swordclient.getStatus();
publish("The status is: " + status);
SwordValidationInfo info = swordclient.getLastUnmarshallInfo();
if( info != null &&
info.getType() == SwordValidationInfoType.VALID)
{
publish("The document was valid");
}
else if( info != null )
{
publish("This document did not validate.");
StringBuffer buffer = new StringBuffer();
info.createString(info, buffer, " ");
publish(buffer.toString());
}
if (status.getCode() == 201
|| status.getCode() == 202) {
mainPanel.processDespositResponse(location,
document);
mainPanel.addMessage(document.marshall());
publish("Data received for location: "
+ location);
} else {
publish("Unable to post file to: "
+ location);
mainPanel.addMessage(document.marshall());
// build up the error message, taking into
// account the exception condition.
String outputMessage;
try{
SWORDErrorDocument errorDoc = document.getErrorDocument();
outputMessage = "Unable to post file to "
+ location
+ ".\r\nStatus is: "
+ status.toString()
+ ".\r\nThe Error URI is: "
+ errorDoc.getErrorURI()
+ "\r\nSummary is: "
+ errorDoc.getSummary();
}catch (SWORDException se){
outputMessage = se.getMessage();
}
// display the error - using the string created above
JOptionPane.showMessageDialog(
GuiClient.this,
outputMessage,
"Post File",
JOptionPane.WARNING_MESSAGE);
}
} catch (MalformedURLException ex) {
publish("Unable to access resource. Error with URL.");
JOptionPane.showMessageDialog(GuiClient.this,
"There is an error with the URL. "
+ ex.getMessage(),
"Service Access Error",
JOptionPane.ERROR_MESSAGE);
} catch (SWORDClientException sce) {
publish("Unable to access resource.");
JOptionPane.showMessageDialog(GuiClient.this,
"There was an error accessing the resource. "
+ sce.getMessage(),
"Service Access Error",
JOptionPane.ERROR_MESSAGE);
}
}
return "Finished";
}
/**
* Called when the worker thread is complete.
*/
@Override
protected void done() {
enableActions(true);
setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
}
/**
* Process the output from the worker thread.
*
* @param chunks
* The list of output to show.
*/
@Override
protected void process(List<String> chunks) {
for (String row : chunks) {
String message = "status: " + row;
mainPanel.addMessage(message);
mainPanel.setStatus(message);
}
}
};
worker.execute();
}
}
/**
* Called when there has been a new selection selected in the service
* dialog. This value is stored and used to set the currently selected
* collection in any post operation.
*
* @param collection
* The url of the post area of a collection.
*/
public void setCollection(String collection) {
this.collection = collection;
}
/**
* Initialise the post dialog. Create it if necessary and then set any
* values from the property store.
*/
protected void initialisePostDialog() {
if (postDialog == null) {
postDialog = new PostDialog(GuiClient.this);
}
// add any known locations from the list of collections in the
// service panel.
String[] locations = mainPanel.getCollectionLocations();
postDialog.addDepositUrls(locations);
String value = props.getProperty("depositurls");
if (value != null) {
String[] services = value.split(",");
postDialog.addDepositUrls(services);
}
value = props.getProperty("users");
if (value != null) {
String[] users = value.split(",");
postDialog.addUserIds(users);
}
value = props.getProperty("formatNamespaceList");
if (value != null) {
String[] namespaces = value.split(",");
postDialog.addFormatNamespaces(namespaces);
}
value = props.getProperty(ON_BEHALF_OF);
if (value != null) {
String[] users = value.split(",");
postDialog.addOnBehalfOf(users);
}
value = props.getProperty("files");
if (value != null) {
String[] files = value.split(",");
postDialog.addFiles(files);
}
value = props.getProperty("fileTypes");
if (value != null) {
String[] fileTypes = value.split(",");
postDialog.addFileTypes(fileTypes);
}
if (collection != null) {
// set the current collection
log.debug("setting collection: " + collection);
postDialog.setDepositLocation(collection);
}
}
}
/**
* Action that process the About operation.
*
* @author Neil Taylor
*/
protected class AboutAction extends AbstractAction {
/**
* Create a new instance.
*/
public AboutAction() {
super("About");
}
/**
* Process the action.
*/
public void actionPerformed(ActionEvent event) {
JOptionPane
.showMessageDialog(
null,
"Demonstration client for SWORD Project - supporting SWORD Profile 1.3\n"
+ "Copyright 2007-2009 CASIS, University of Wales Aberystwyth\n\n"
+ "Version "
+ ClientConstants.CLIENT_VERSION, "About",
JOptionPane.INFORMATION_MESSAGE);
}
}
/**
* Action that process the Help operation.
*
* @author Neil Taylor
*/
protected class HelpAction extends AbstractAction {
/**
* Create a new instance.
*/
public HelpAction() {
super("Help");
}
/**
* Display the help information.
*
* @param event
* The event that generated this call.
*/
public void actionPerformed(ActionEvent event) {
try {
File helpDir = File.createTempFile("swordHelp", null);
if (!helpDir.delete()) {
throw new IOException("Couldn't create tmp dir: " + helpDir);
}
if (!helpDir.mkdirs()) {
throw new IOException("Couldn't create tmp dir: " + helpDir);
}
extractHelp(helpDir);
String osname = System.getProperty("os.name");
log.info("osname is: " + osname);
String runCmd = "";
if ("Mac OS X".equals(osname)) {
runCmd = "open";
} else if ("Windows XP".equals(osname)) {
runCmd = "rundll32 url.dll,FileProtocolHandler";
} else if ("Linux".equals(osname)) {
// Take a punt on firefox!
runCmd = "firefox";
} else {
log.error(osname + " not supported.");
}
String helpFile = helpDir.getCanonicalPath() + File.separator
+ "index.html";
Runtime.getRuntime().exec(runCmd + " " + helpFile);
} catch (IOException ioe) {
log.error("Error accessing help files");
ioe.printStackTrace();
} catch (URISyntaxException e) {
log.error("Error accessing help files");
e.printStackTrace();
}
}
/**
* Here be dragons. Extracting the help resources from a jar file to a
* temporary directory where the user's web browser can get at them is
* gnarly.
*
* @param helpDir
* @throws URISyntaxException
* @throws IOException
*/
private void extractHelp(File helpDir) throws URISyntaxException,
IOException {
ClassLoader cl = getClass().getClassLoader();
URL help = cl.getResource("help");
if ("file".equals(help.getProtocol())) {
File from = new File(help.toURI());
FileUtils.copyDirectory(from, helpDir);
} else if ("jar".equals(help.getProtocol())) {
// This is ugly. If there's a way round, I'd love to know about
// it.
// Strip between 'jar:file:'
log.debug("Help url: " + help);
String jarLoc = help.toString().substring(9,
help.toString().lastIndexOf("!"));
File f = new File(jarLoc);
if (!f.exists()) {
log
.error("Cannot display help - can't find help files to make a local temp copy");
}
JarFile jarFile = new JarFile(jarLoc);
for (Enumeration<JarEntry> entries = jarFile.entries(); entries
.hasMoreElements();) {
JarEntry je = entries.nextElement();
if (je.getName().startsWith("help/")) {
log.debug(je.getName() + " | Directory? "
+ je.isDirectory());
// Trim the 'help/' off and fix up the file separators
String filename = je.getName().substring(5).replaceAll(
"/", File.separator);
File destination = new File(helpDir, filename);
File directory = je.isDirectory() ? destination
: destination.getParentFile();
log.debug("Creating " + directory
+ " and copying resource to " + destination);
if (!(directory.exists() || directory.mkdirs())) {
throw new IOException(
"Problem creating temp help directory, couldn't create: "
+ directory);
}
if (!je.isDirectory()) {
FileUtils.copyURLToFile(cl
.getResource(je.getName()), destination);
}
}
}
} else {
throw new RuntimeException(
"Don't know how to unpack help files from " + help);
}
}
}
/**
* Respond to the notification that a collection has been selected in the
* Service panel.
*
* @see org.purl.sword.client.ServiceSelectedListener#selected()
*/
public void selected(String value) {
postAction.setCollection(value);
}
/***************************************************************************
* Panels
*/
/**
* The main panel in the GUI application. This host a service panel, at the
* top, and a message panel at the bottom. Methods are provided to allow
* messages to be displayed in the message panel.
*/
protected class MainPanel extends JPanel {
/**
* The message output panel. This is displayed in the tabbedMessages
* pane.
*/
private MessageOutputPanel messages = null;
/**
* The service panel. This displayes the list of services and references
* to posted files.
*/
private ServicePanel services = null;
/**
* The tabbed pane that contains the messages and debug panels.
*/
private JTabbedPane tabbedMessages = null;
/**
* The debug output panel. This is displayed in the tabbedMessages pane.
*/
private MessageOutputPanel debugPanel = null;
/**
* The status part of the screen.
*/
private JLabel statusLabel = null;
/**
* Create a new instance.
*
* @param noCaptureOutput
* If true, the System.out and System.err streams will not be
* captured and shown in a debug panel. Otherwise, the output
* will be captured.
*/
public MainPanel(boolean noCaptureOutput) {
super(new BorderLayout());
log.debug("Constructing MainPanel ...");
services = new ServicePanel();
services.setServiceSelectedListener(GuiClient.this);
messages = new MessageOutputPanel();
debugPanel = new MessageOutputPanel();
DebugOutputStream output = new DebugOutputStream(debugPanel);
if (!noCaptureOutput) {
log.debug("Capturing output ...");
System.setErr(new java.io.PrintStream(output));
System.setOut(new java.io.PrintStream(output));
// reset the log4j tool because the streams have changed.
PropertyConfigurator.configure(getClass().getClassLoader()
.getResource(ClientConstants.LOGGING_PROPERTY_FILE));
}
tabbedMessages = new JTabbedPane();
tabbedMessages.addTab("Messages", messages);
JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
services, tabbedMessages);
splitPane.setOneTouchExpandable(true);
splitPane.setResizeWeight(0.5);
splitPane.setDividerLocation(300);
statusLabel = new JLabel(" ");
add(splitPane, BorderLayout.CENTER);
add(statusLabel, BorderLayout.SOUTH);
}
/**
* Show or hide the debug panel.
*
* @param enabled
* True if the debug panel should be shown. False if the
* panel should be hidden.
*/
public void showDebugTab(boolean enabled) {
if (enabled) {
if (tabbedMessages.getTabCount() == 1) {
tabbedMessages.addTab("Debug", debugPanel);
tabbedMessages.setSelectedComponent(debugPanel);
}
} else {
if (tabbedMessages.getTabCount() == 2) {
tabbedMessages.remove(debugPanel);
}
}
}
/**
* Add a message to the main panel.
*
* @param message
* The message to display.
*/
public void addMessage(String message) {
messages.addMessage(message);
}
/**
* Process the service document and display the details in the service
* panel.
*
* @param url
* The original URL that was accessed to obtain the service
* document.
* @param service
* The service document.
*/
public void processServiceDocument(String url,
ServiceDocument service) {
services.processServiceDocument(url, service);
}
/**
* Process a deposit response and display the information in the service
* panel.
*
* @param response
* The DepositResponse to process.
*/
public void processDespositResponse(String url,
DepositResponse response) {
services.processDepositResponse(url, response);
}
/**
* Get the list of collection locations.
*
* @return A list of collection locations.
*/
public String[] getCollectionLocations() {
return services.getCollectionLocations();
}
/**
* Set the status label on the panel.
*
* @param statusMessage
*/
public void setStatus(String statusMessage) {
statusLabel.setText(statusMessage);
}
}
}