[MOBY-guts] biomoby commit
Paul Gordon
gordonp at dev.open-bio.org
Tue Jun 9 19:29:11 UTC 2009
gordonp
Tue Jun 9 15:29:11 EDT 2009
Update of /home/repository/moby/moby-live/Java/src/main/ca/ucalgary/seahawk/gui
In directory dev.open-bio.org:/tmp/cvs-serv21143/src/main/ca/ucalgary/seahawk/gui
Modified Files:
MobyContentPane.java
Log Message:
Whole shwack of changes to track drag 'n' drop and related functionality required for Daggoo service wrapping
moby-live/Java/src/main/ca/ucalgary/seahawk/gui MobyContentPane.java,1.17,1.18
===================================================================
RCS file: /home/repository/moby/moby-live/Java/src/main/ca/ucalgary/seahawk/gui/MobyContentPane.java,v
retrieving revision 1.17
retrieving revision 1.18
diff -u -r1.17 -r1.18
--- /home/repository/moby/moby-live/Java/src/main/ca/ucalgary/seahawk/gui/MobyContentPane.java 2008/01/14 23:00:45 1.17
+++ /home/repository/moby/moby-live/Java/src/main/ca/ucalgary/seahawk/gui/MobyContentPane.java 2009/06/09 19:29:11 1.18
@@ -2,7 +2,10 @@
package ca.ucalgary.seahawk.gui;
// For external links
+import ca.ucalgary.seahawk.services.DaggooClient;
import ca.ucalgary.seahawk.util.*;
+import ca.ucalgary.services.SoapServlet;
+import ca.ucalgary.services.util.PBERecorder;
import org.biomoby.client.MobyRequest;
import org.biomoby.client.MobyRequestEvent;
@@ -23,6 +26,7 @@
import javax.swing.event.*;
import java.awt.*;
+import java.awt.datatransfer.*;
import java.awt.print.*;
import java.awt.event.*;
import java.io.*;
@@ -35,11 +39,16 @@
* Also provides link handling (including service options popup) and drag 'n' drop capabilities.
*/
-public class MobyContentPane extends JPanel implements Printable, CaretListener, HyperlinkListener, MouseListener, MobyRequestEventHandler, KeyListener, ChangeListener{
+public class MobyContentPane extends JPanel implements Printable, CaretListener, HyperlinkListener, MouseListener, MouseMotionListener, MobyRequestEventHandler, KeyListener, ChangeListener, ClipboardOwner, ActionListener{
public static final String MOBY_SERVICE_POPUP_NAME = "seahawkServicePopup";
public static final String WAITING_TAB_ICON_RESOURCE = "ca/ucalgary/seahawk/resources/images/hourglass.gif";
public static final String FAILED_TAB_ICON_RESOURCE = "ca/ucalgary/seahawk/resources/images/failed.gif";
public static final String LOADED_TAB_ICON_RESOURCE = "ca/ucalgary/seahawk/resources/images/document.gif";
+ // An undocumented "feature" of Java temp file creation is that assigned prefixes are turned to lower case,
+ // therefore you want to makr the prefix lower case if you're going to check for this prefix's existence in a file name
+ // anywhere in your code.
+ public static final String WSDL_RESULTFILE_PREFIX = "wsdl_results_parsed";
+ public static final String SERVICE_CREATION_MSG = "Create a service returning this datatype";
private static final int TAB_ICON_SPACER = 2;
private static ImageIcon hourglassIcon;
@@ -65,11 +74,29 @@
private boolean isContentsXML = false;
private DataRecorder dataRecorder;
+ // Next two items used for service wrapping
+ private JMenuItem createServicePopupItem;
+ private MobyDataInstance itemToReturn;
+
// Text selection members
private int dot;
private int mark;
+ private int oldMark;
+ private int oldDot;
+ private boolean dragging = false;
+ private boolean isWrapping = false; // are we wrapping a service using PBE at the moment?
private String selectedTextData;
+ private boolean overHyperlink = false;
+ private URL lastHyperlinkDragged = null;
+ private URL lastHyperlinkHovered = null;
+
private static org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(MobyContentPane.class);
+ private static SoapServlet soapServlet;
+ private final int portNum = 8254;
+ private final static String servletPath = "/SOAPServlet";
+ private static Acme.Serve.Serve servletContainer;
+ private static Map<String,MobyContentGUI> id2GuiMap;
+
public MobyContentPane(MobyContentGUI cGUI, MobyServicesGUI sGUI, JTabbedPane parentComponent, DataRecorder recorder, JLabel statusBar){
tabbedPane = parentComponent;
status = statusBar;
@@ -90,11 +117,15 @@
//editorPane.setPreferredSize(parentComponent.getPreferredSize());
editorPane.setEditable(false);
editorPane.addMouseListener(this);
+ editorPane.addMouseMotionListener(this);
editorPane.addHyperlinkListener(this);
editorPane.addCaretListener(this);
scrollPane = new JScrollPane(editorPane);
add(scrollPane);
+ createServicePopupItem = new JMenuItem(SERVICE_CREATION_MSG);
+ createServicePopupItem.addActionListener(this);
+
history = new Vector<URL>();
historyTabLabels = new HashMap<URL,String>();
@@ -129,6 +160,8 @@
loadedIcon = new ImageIcon(u);
}
}
+
+ id2GuiMap = new HashMap<String,MobyContentGUI>();
}
public void stateChanged(ChangeEvent ce){
@@ -160,6 +193,53 @@
}
}
+ public void actionPerformed(ActionEvent e){
+ if(e.getSource() == createServicePopupItem){
+ if(itemToReturn != null){
+ createService();
+ }
+ }
+ else{
+ logger.warn("Content pane: ignoring unrecognized source of event " + e);
+ }
+ }
+
+ private void createService(){
+ // item name, set by MobyContentGUI's processResults(), is namedXPath#numericXPtr
+ String[] locSpec = itemToReturn.getName().split("#");
+ String xPath = locSpec[0];
+ if(locSpec.length == 1){
+ logger.warn("Could not find a '#' in object name " + itemToReturn.getName());
+ return;
+ }
+ String xPtr = locSpec[1];
+
+ // remove the last part of the xPath, which is actually the value, not the selection
+ String valXPath = xPath.substring(xPath.lastIndexOf("/")+1);
+ xPath = xPath.substring(0, xPath.lastIndexOf("/"));
+
+ //todo: MOBY_OUTPUT_NS_PARAM MOBY_OUTPUT_TYPE_PARAM
+ String transformationParam = "";
+ if(locSpec.length == 3){
+ transformationParam = "&"+PBERecorder.OUTPUT_RULE_URI_PARAM+"="+locSpec[2];
+ }
+
+ // we assume the wrapping form has already been launched
+ // so we'll get the cookie and cotinue the process at the xpath selection stage
+ URL formURL = null;
+ try{
+ formURL = new URL("http://localhost:"+portNum+servletPath+"?"+
+ PBERecorder.CONTEXT_XPTR_PARAM+"="+xPtr+"&"+
+ PBERecorder.RETURN_XPATH_PARAM+"="+xPath+"&"+
+ PBERecorder.VALUE_XPATH_PARAM+"="+valXPath+
+ transformationParam);
+ } catch (java.net.MalformedURLException murle){
+ logger.error("Could not create URL for wrapping form: " + murle);
+ return;
+ }
+ launchInWebBrowser(formURL);
+ }
+
public void setPreferredSize(Dimension dims){
super.setPreferredSize(dims);
scrollPane.setPreferredSize(dims);
@@ -180,6 +260,91 @@
return editorPane.getText();
}
+ /**
+ * Launch a servlet create a CGI form from a WSDL document, and that'll capture user events
+ * while filling in the form to call a web service. This'll allow a Moby service to be created
+ * for later calling.
+ */
+ private void loadWSDL(URL u){
+
+ if(DaggooClient.acceptAnExistingWrapper(u, this)){
+ return;
+ }
+
+ // Launch the wrapping servlet if not launched yet
+ editorPane.setText("Launching service wrapper interface...");
+
+ // Launch the servlet
+ if(contentGUI.getServletContainer() == null){
+ soapServlet = new SoapServlet();
+
+ final Acme.Serve.Serve srv = new Acme.Serve.Serve();
+ java.util.Properties properties = new java.util.Properties();
+ properties.put("port", portNum);
+ srv.arguments = properties;
+ srv.addServlet(servletPath, soapServlet); // our deployment
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ public void run() {
+ try {
+ srv.notifyStop();
+ }catch(Exception e) {
+ // May throw NullPointerException if server was already killed,
+ // or maybe an IOException otherwise
+ logger.warn("Trouble shutting down SOAP-wrapping servlet on port "
+ + portNum + ": " + e);
+ }
+ srv.destroyAllServlets();
+ }
+ }));
+ new Thread(){public void run(){srv.serve();}}.start();
+
+ PBERecorder recorder = new PBERecorder();
+ recorder.setGUIMap(id2GuiMap);
+ soapServlet.setRecorder(recorder);
+ // So we can stop the container later, assign the final var to a class scope one.
+ contentGUI.setServletContainer(srv);
+ }
+
+ // uniq id and GUI link up allow unification of browser events and the Sehawk GUI for PBE
+ String uniqID = ""+Math.random();
+ id2GuiMap.put(uniqID, contentGUI);
+
+ URL formURL = null;
+ try{
+ formURL = new URL("http://localhost:"+portNum+servletPath+"?"+
+ SoapServlet.WSDL_HTTP_PARAM+"="+u+"&"+
+ SoapServlet.ID_PARAM+"="+uniqID);
+ } catch (java.net.MalformedURLException murle){
+ logger.error("Could not create URL for wrapping form: " + murle);
+ return;
+ }
+ launchInWebBrowser(formURL);
+ }
+
+ private void launchInWebBrowser(URL u){
+ // Launch the browser with the form the servlet hosts
+ try{
+ Desktop desktop = Desktop.getDesktop();
+ if(!desktop.isSupported(Desktop.Action.BROWSE)) {
+ editorPane.setText("Your Java does not support external browser launch. " +
+ "Please manually launch a browser and go to the URL " +
+ u);
+ }
+ desktop.browse(u.toURI());
+ } catch (java.net.URISyntaxException urise){
+ logger.error("Could not create URI from URL ("+u+"): " + urise);
+ } catch (java.io.IOException ioe){
+ logger.error("Could not launch browser for form (" + u + "): " + ioe);
+ }
+ }
+
+ /**
+ * Tells whether the tab is in the middle of wrapping a Web Service at the moment.
+ */
+ public boolean isWrappingService(){
+ return isWrapping;
+ }
+
/**
* This is the URLLoader callback method the MOBY data fetcher
* will call when MOBY XML data is ready to be seen.
@@ -200,11 +365,31 @@
URLConnection urlCon = url.openConnection();
boolean unformatted = false;
+ // See if it's a WSDL file. If so, launch the service wrapper functionality
+ if(urlString.endsWith(".wsdl")){
+ loadWSDL(url);
+ isWrapping = true;
+ return; // don't load the file in the pane
+ }
+ else{
+ isWrapping = false;
+ }
+
// It's XML that needs to be transformed to HTML
if(urlString.endsWith(".xml") ||
"text/xml".equals(urlCon.getContentType())){
editorPane.setContentType("text/html");
isContentsXML = true;
+ // Is it the second stage of wrapping, where data results are being seen?
+ if(urlString.indexOf(WSDL_RESULTFILE_PREFIX) != -1){
+ System.err.println("Wrapping service results");
+ isWrapping = true;
+ }
+ else{
+ //System.err.println("Not wrapping service results, xml file name ("+
+ // urlString +
+ // ") does not contain " + WSDL_RESULTFILE_PREFIX);
+ }
// Tell the stylesheet the URL of the moby data (it will create xpointers to it)
contentGUI.getTransformer().setParameter(XSL_DOC_SOURCE_PARAM, url.toString());
@@ -227,13 +412,26 @@
catch(TransformerException te){
status.setText("Sorry! Could not transform the MOBY data into presentation form");
logger.error("Sorry! Could not transform the MOBY data into presentation form: " + te);
- return;
+ return; // don't load the file in the pane
}
catch(Exception e){
- status.setText("Note: There was an error transforming the MOBY data " +
- "into presentation form, it may not be displayed correctly");
- logger.error("Note: There was an error transforming the MOBY data " +
- "into presentation form, it may not be displayed correctly", e);
+ // check to see if it's wsdl without a .wsdl suffix...
+ String body = HTMLUtils.getURLContents(url);
+ if(body.indexOf("definitions ") != -1 &&
+ body.indexOf("\"http://schemas.xmlsoap.org/wsdl/\"") != -1){
+ loadWSDL(url);
+ isWrapping = true;
+ }
+ // TODO if the data in XML that can be converted to MOBY...
+ // else if(){
+ //}
+ else{
+ status.setText("Note: There was an error transforming the MOBY data " +
+ "into presentation form, it may not be displayed correctly");
+ logger.error("Note: There was an error transforming the MOBY data " +
+ "into presentation form, it may not be displayed correctly", e);
+ }
+ return;
}
// Get rid of inter-tag whitespaces
htmlContents = htmlContents.replaceAll(">[ \\t\\r\\n]+<", "><");
@@ -287,13 +485,20 @@
}
//editorPane.setContentType("text/plain");
isContentsXML = false;
- //editorPane.setFont(java.awt.Font.getFont("Monospaced"));
+ //editorPane.setFont(Font.getFont("Monospaced"));
editorPane.setContentType("text/html");
String body = HTMLUtils.getURLContents(url);
if(body.toLowerCase().indexOf("<html") != -1){
resultBuffer = null;
}
else{
+ // one last check to see if it's wsdl without a .wsdl suffix...
+ if(body.indexOf("definitions ") != -1 &&
+ body.indexOf("\"http://schemas.xmlsoap.org/wsdl/\"") != -1){
+ loadWSDL(url);
+ isWrapping = true;
+ return; // don't load the file in the pane
+ }
resultBuffer = new StringBuffer("<html><body><pre>"+
body.replaceAll("&", "&").replaceAll("<","<")+
"</pre></body></html>");
@@ -398,6 +603,17 @@
}
/**
+ * Called after data copied from this pane is pasted. The actual text pasted in given
+ * so that it can be unified with the object that created it (lastURLDragged) in the
+ * PBE recorder.
+ */
+ public void exportDone(String valuePasted, String transformRuleURI){
+ if(soapServlet != null && soapServlet.getRecorder() != null){
+ soapServlet.getRecorder().dataCopied(getDraggedData(), valuePasted, transformRuleURI);
+ }
+ }
+
+ /**
* Called by MOBYRequest when the service request is being sent. We
* can create the GUI and wait message here.
*/
@@ -490,7 +706,9 @@
tabbedPane.setDisabledIconAt(tabIndex, loadedIcon);
tabbedPane.setTitleAt(tabIndex, msg);
// Store the latest title for the URL loaded, in case we revisit
- historyTabLabels.put(history.elementAt(historyIndex), msg);
+ if(historyIndex != -1){
+ historyTabLabels.put(history.elementAt(historyIndex), msg);
+ }
hasFailed = false;
}
@@ -501,7 +719,7 @@
public String toScufl() throws Exception{
URL[] historyToExport = (URL[]) history.toArray(new URL[history.size()]);
if(historyIndex != historyToExport.length-1){
- // Shorten the array if we aren't cuurently on the last doc (i.e.
+ // Shorten the array if we aren't currently on the last doc (i.e.
// we don't want to export documents forward in the history)
URL[] truncatedHistory = new URL[historyIndex];
System.arraycopy(historyToExport, 0, truncatedHistory, 0, historyIndex);
@@ -673,20 +891,15 @@
// External, hopefully HTML link
else{
status.setText("Launching Web browser for external link...");
- try{
- BrowserLauncher.openURL(targetURL.toString());
- }
- catch(IOException ioe){
- status.setText("Couldn't launch external browser");
- logger.debug("Couldn't launch external browser for " + targetURL +
- " because of I/O exception: " + ioe);
- }
+ launchInWebBrowser(targetURL);
}
}
if(he.getEventType() == HyperlinkEvent.EventType.ENTERED){
+ overHyperlink = true;
+ lastHyperlinkHovered = targetURL;
if(isMobyURL(targetURL)){
- status.setText("Click to discover more MOBY services");
+ status.setText("Click to discover more Moby services, or drag onto a Web form");
}
else if(targetURL == null){
String desc = he.getDescription();
@@ -700,29 +913,36 @@
}
}
if(he.getEventType() == HyperlinkEvent.EventType.EXITED){
+ overHyperlink = false;
status.setText("");
}
}
- protected void showMobyOptions(URL targetURL){
+ public MobyDataInstance getDraggedData(){
+ return urlToMobyData(lastHyperlinkDragged);
+ }
+
+ protected MobyDataInstance urlToMobyData(URL targetURL){
// Build a MOBY object using the URL info
// Links have the form moby_xml_url#child_xpath refering to complex objects
// or http://moby/namespace?id=moby_id&string=cdata_value for simple objects and strings
- String docFragID = targetURL
-.getRef(); // We store the xpath in the anchor part of the URL
-
- MobyDataInstance mobyData = null;
- // Complex case, load the doc fragment from the MOBY XML source file
+ String docFragID = targetURL.getRef(); // We store the xpath in the anchor part of the URL
- if(docFragID != null && docFragID.length() > 0 && contentGUI.getDocumentBuilder() != null){
- mobyData = loadMobyDataFromXPointer(targetURL);
+ // Complex case, load the doc fragment from the MOBY XML source file
+ if(docFragID != null && docFragID.length() > 0 && contentGUI.getDocumentBuilder() != null &&
+ !targetURL.getHost().equals("moby")){
+ return loadMobyDataFromXPointer(targetURL);
}
// Simple case, build the object using the values encoded in the URL itself
else{
- mobyData = loadMobyDataFromURLQuery(targetURL);
+ return loadMobyDataFromURLQuery(targetURL);
}
+ }
+
+ protected void showMobyOptions(URL targetURL){
+ MobyDataInstance mobyData = urlToMobyData(targetURL);
if(mobyData == null){
logger.warn("Cannot create MOBY service list, could " +
@@ -749,9 +969,17 @@
}
/**
- * Subclasses can use this method to expand the popup menu.
+ * Subclasses can use this method to expand the popup menu. Be sure to call super.addExtraMobyOptions()
*/
protected void addExtraMobyOptions(JPopupMenu popup, MobyDataInstance mdi){
+ // If we are wrapping a service, and you're getting the context menu, you must be
+ // looking at service results, in which case let's give the option to create a service.
+ if(isWrapping){
+ itemToReturn = mdi;
+ popup.add(createServicePopupItem);
+ popup.setVisible(true);
+ }
+
}
/**
@@ -823,6 +1051,11 @@
else if(mobyName.length() == 0 && token.startsWith("name=") && token.length() >= 5){
if(token.length() > 5){
mobyName = token.substring(5);
+ // The name may also contain #xxx, which will be in the ref part of
+ // the URL and needs to be appended.
+ if(targetURL.getRef() != null){
+ mobyName += "#"+targetURL.getRef();
+ }
}
else{
mobyName = "";
@@ -975,7 +1208,7 @@
}
// MouseListener interface implementation
- public void mouseClicked(MouseEvent e){
+ public void mousePressed(MouseEvent e){
// Allow middle button paste
if(e.getButton() == MouseEvent.BUTTON2 ||
e.getButton() == MouseEvent.BUTTON3 && e.isAltDown()){
@@ -1005,12 +1238,28 @@
public void mouseExited(MouseEvent e){}
- public void mousePressed(MouseEvent e){
+ public void mouseClicked(MouseEvent e){
lastClickX = e.getX();
lastClickY = e.getY();
if(checkSelectionPressed(lastClickX, lastClickY)){
if(selectedTextData == null){
- return;
+ // If there is no selected text, we need to reconstruct it from the info we have
+ // about what was highlighted just before.
+ if(oldMark != oldDot){
+ try{
+ if(oldMark > oldDot){
+ selectedTextData = editorPane.getText(oldDot, oldMark-oldDot);
+ }
+ else{
+ selectedTextData = editorPane.getText(oldMark, oldDot-oldMark);
+ }
+ } catch(javax.swing.text.BadLocationException ble){
+ logger.warn("Bad dot/mark record for getText(): " + ble);
+ }
+ finally{
+ oldMark = oldDot; // clear the old selection so only the first click activates the above code
+ }
+ }
}
MobyDataInstance[] mobyData = loadMobyDataFromString(selectedTextData);
if(mobyData == null || mobyData.length == 0){
@@ -1043,10 +1292,18 @@
}
protected boolean checkSelectionPressed(int x, int y){
+ int pressedLoc = editorPane.viewToModel(new Point(x, y));
if(mark == dot){
- return false; // nothing selected
+ if(oldMark < oldDot && (pressedLoc >= oldMark && pressedLoc <= oldDot) ||
+ oldMark > oldDot && (pressedLoc <= oldMark && pressedLoc >= oldDot)){
+ // clicked on a spot that was just highlighted previously (but caret events happen before mouse events)
+ return true;
+ }
+ else{
+ oldMark = oldDot;
+ return false; // nothing selected
+ }
}
- int pressedLoc = editorPane.viewToModel(new Point(x, y));
if(mark < dot && (pressedLoc >= mark && pressedLoc <= dot) ||
mark > dot && (pressedLoc <= mark && pressedLoc >= dot)){
return true;
@@ -1075,8 +1332,12 @@
//status.setText("selection canceled");
return;
}
+ else{ // keep track of the previous selection so we can use it once it's gone
+ oldMark = mark;
+ oldDot = dot;
+ }
selectedTextData = editorPane.getSelectedText();
- status.setText("Click your selection to use as MOBY data!");
+ status.setText("Click your selection to use it as MOBY data!");
}
/**
@@ -1102,6 +1363,43 @@
public void keyTyped(KeyEvent e){}
public void paste(){
- getTransferHandler().importData(this, java.awt.Toolkit.getDefaultToolkit().getSystemClipboard().getContents(this));
+ getTransferHandler().importData(this, Toolkit.getDefaultToolkit().getSystemClipboard().getContents(this));
+ }
+
+ public void mouseDragged(MouseEvent e){
+ // if a hyperlink is being dragged, and we're wrapping, this should be a copy event
+ if(overHyperlink && !dragging){
+ dragging = true; // so we don't export many times in a row while dragging
+ lastHyperlinkDragged = lastHyperlinkHovered;
+ status.setText("Drop the hyperlink onto a Web form field to populate it");
+ System.err.println("Dragging "+lastHyperlinkDragged);
+ getTransferHandler().exportToClipboard(this,
+ Toolkit.getDefaultToolkit().getSystemClipboard(),
+ TransferHandler.COPY);
+ getTransferHandler().exportAsDrag(this, e, TransferHandler.COPY);
+ }
+ else if(!dragging){
+ System.err.println("Dragging with no hyperlink activated");
+ }
+ }
+
+ public void mouseMoved(MouseEvent e){
+ // if a hyperlink is being dragged, and we're wrapping, this should be a copy event
+ // normally it'd go to mouseDragged, but sometimes that's flakey so check manually for the
+ // mouse button being down here.
+ if(overHyperlink && (e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) == MouseEvent.BUTTON1_DOWN_MASK){
+ System.err.println("Got wonky drag");
+ lastHyperlinkDragged = lastHyperlinkHovered;
+ status.setText("Drop the hyperlink onto a Web form field to populate it");
+ System.err.println("Dragging "+lastHyperlinkDragged);
+ getTransferHandler().exportAsDrag(this, e, TransferHandler.COPY);
+ }
+ else if(dragging){
+ dragging = false;
+ }
+ }
+
+ public void lostOwnership(Clipboard clipboard, Transferable contents){
+ System.err.println("Lost clipboard ownership");
}
}
More information about the MOBY-guts
mailing list