// RegistrationServiceSubPanel.java
//
// Created: October 2005
//
// This file is a component of the BioMoby project.
// Copyright Martin Senger (martin.senger@gmail.com).
//

package org.biomoby.service.dashboard;

import org.biomoby.shared.MobyException;
import org.biomoby.shared.MobyNamespace;
import org.biomoby.shared.MobyService;
import org.biomoby.shared.MobyPrimaryData;
import org.biomoby.shared.MobySecondaryData;
import org.biomoby.shared.MobyDataType;
import org.biomoby.shared.MobyData;

import org.tulsoft.shared.UUtils;
import org.tulsoft.tools.gui.SwingUtils;
import org.tulsoft.tools.gui.JTextFieldWithHistory;
import org.tulsoft.tools.gui.JFileChooserWithHistory;

import javax.swing.JPanel;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JButton;
import javax.swing.JRadioButton;
import javax.swing.ButtonGroup;
import javax.swing.JCheckBox;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.JTabbedPane;
import javax.swing.JComponent;
import javax.swing.Box;
import javax.swing.SwingUtilities;
import javax.swing.tree.DefaultMutableTreeNode;

import java.awt.GridBagLayout;
import java.awt.Component;
import java.awt.event.KeyEvent;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
import java.io.File;
import java.io.IOException;

/**
 * A panel allowing to register a service in a Biomoby registry. <p>
 *
 * @author <A HREF="mailto:martin.senger@gmail.com">Martin Senger</A>
 * @version $Id: RegistrationServiceSubPanel.java,v 1.14 2009/05/19 21:03:56 kawas Exp $
 */

public class RegistrationServiceSubPanel
    extends RegistrationPanel
    implements PropertyChangeListener {

    // names of user preferences keys
    static final String USE_SIGNATURE = "use-signature";
    static final String USE_AUTHORITATIVE = "use-authoritative";
    static final String REG_S_TYPE = "reg-s-type";
    static final String REG_S_FROM_XML = "reg-service-from-xml-file";
    static final String COPY_BY_SELECT_S = "s-copy-by-select";

    // components that are used from more methods
    JTextFieldWithHistory sName, sAuth, sEmail, sURL, sSigURL;
    JComboBox sCategories;
    JTextArea sDescArea;
    JLabel sType, labelSigURL, labelRDFPath;
    JFileChooserWithHistory localRDFFile;
    JCheckBox useAuth, useSignature;
    JRadioButton rbInputs, rbOutputs;
    PrimaryDataTable primInTable, primOutTable;
    SecondaryDataTable secTable;
    JButton unregisButton, agentButton;

    /*********************************************************************
     * Default constructor.
     ********************************************************************/
    public RegistrationServiceSubPanel() {
	super();
    }

    /**************************************************************************
     * Panel to register a service.
     **************************************************************************/
    public JComponent getComponent (PropertyChannel propertyChannel,
				    CommonConsole console) {
 	setPropertyChannel (propertyChannel);
	registryModel = createRegistryModel();
	this.console = console;
	this.propertyChannel.addPropertyChangeListener (this);

	JPanel p = new JPanel (new GridBagLayout());

	// text and combo fields to define new service
   	JLabel labelSName = new JLabel ("Service name");
	sName = createText (null, "serviceName", DP_REG_S_NAME);
   	JLabel labelSAuth = new JLabel ("Authority");
	sAuth = createText (null, "serviceAuth", DP_REG_S_AUTH);
   	JLabel labelSEmail = new JLabel ("Contact email");
	sEmail = createText (null, "serviceEmail", DP_REG_S_EMAIL);

   	JLabel labelSCat = new JLabel ("Service category");
        sCategories =
	    new JComboBox ( new String[] {
		MobyService.CATEGORY_MOBY,
		MobyService.CATEGORY_MOBY_ASYNC,
		MobyService.CATEGORY_MOBY_DOCLIT,
		MobyService.CATEGORY_MOBY_DOCLIT_ASYNC,
		MobyService.CATEGORY_CGI,
		MobyService.CATEGORY_CGI_ASYNC
	    });
        sCategories.setToolTipText ("What kind of BioMoby service you wish to register");
	sCategories.setSelectedItem (getPrefValue (DP_REG_S_CATEGORY,
						  MobyService.CATEGORY_MOBY));
	sCategories.addActionListener (new ActionListener() {
		public void actionPerformed (ActionEvent e) {
		    String contents =  (String)((JComboBox)e.getSource()).getSelectedItem();
		    setPrefValue (DP_REG_S_CATEGORY, contents);
		}
	    });

   	JLabel labelSURL = new JLabel ("Service endpoint - URL");
	sURL = createText (null, "serviceURL", DP_REG_S_URL);
	boolean usingAuth = getPrefValue (USE_AUTHORITATIVE, true);
	useAuth =
	    createCheckBox ("authoritative", usingAuth, KeyEvent.VK_A,
			    new ItemListener() {
				public void itemStateChanged (ItemEvent e) {
				    onAuth (e.getStateChange() == ItemEvent.SELECTED);
				}
			    });
	onAuth (usingAuth);

	// group of fields for RDF signature
   	labelSigURL = new JLabel ("RDF endpoint - signature URL");
	sSigURL = createText (null, "signatureURL", DP_REG_S_RDF_URL);
   	labelRDFPath = new JLabel ("Where to store RDF document");
	String defValue =
	    System.getProperty ("java.io.tmpdir") +
	    File.separator +
	    "service.rdf";
	localRDFFile = createFileSelector ("File name for RDF service signature",
					   "Select",
					   defValue,
					   "localRDFFile",
					   DP_REG_S_RDF_PATH);
	boolean usingSignature = getPrefValue (USE_SIGNATURE, false);
	useSignature =
	    createCheckBox ("Use RDF signature", usingSignature, KeyEvent.VK_R,
			    new ItemListener() {
				public void itemStateChanged (ItemEvent e) {
				    onUseRDFSignature (e.getStateChange() == ItemEvent.SELECTED);
				}
			    });
	onUseRDFSignature (usingSignature);

	JPanel pRDF = createTitledPanel ("Service RDF Signature");
 	SwingUtils.addComponent (pRDF, useSignature, 0, 0, 1, 1, NONE, NWEST, 0.0, 0.0);
  	SwingUtils.addComponent (pRDF, labelSigURL,  0, 1, 1, 1, NONE, NWEST, 0.0, 0.0, BREATH_TOP);
	SwingUtils.addComponent (pRDF, sSigURL,      0, 2, 1, 1, HORI, NWEST, 1.0, 0.0);
 	SwingUtils.addComponent (pRDF, labelRDFPath, 0, 3, 1, 1, NONE, NWEST, 0.0, 0.0);
	SwingUtils.addComponent (pRDF, localRDFFile, 0, 4, 1, 1, HORI, NWEST, 1.0, 0.0);

	// service type
	JPanel serviceType = new JPanel (new GridBagLayout());
   	JLabel labelST = new JLabel ("Service type: ");
	sType = new JLabel (getPrefValue (REG_S_TYPE, ""));
 	SwingUtils.addComponent (serviceType, labelST, 0, 0, 1, 1, NONE, NWEST,  0.0, 0.0);
 	SwingUtils.addComponent (serviceType, sType,   1, 0, 1, 1, NONE, NWEST,  0.0, 0.0);

	// copy-by-select
	copyBySelect =
	    createCheckBox ("Fill new Service when selected in browser panel",
			    false, KeyEvent.VK_C, null);

	// put together service fields
	JPanel defs = new JPanel (new GridBagLayout());
 	SwingUtils.addComponent (defs, labelSName,       0, 0, 1, 1, NONE, NWEST,  0.0, 0.0);
 	SwingUtils.addComponent (defs, sName,            0, 1, 1, 1, HORI, NWEST,  1.0, 0.0);
 	SwingUtils.addComponent (defs, useAuth,          1, 1, 1, 1, NONE, NWEST,  0.0, 0.0, BREATH_LEFT);
 	SwingUtils.addComponent (defs, labelSAuth,       0, 2, 2, 1, NONE, NWEST,  0.0, 0.0);
 	SwingUtils.addComponent (defs, sAuth,            0, 3, 2, 1, HORI, NWEST,  1.0, 0.0);

 	SwingUtils.addComponent (defs, labelSEmail,      0, 4, 1, 1, NONE, NWEST,  0.0, 0.0);
 	SwingUtils.addComponent (defs, sEmail,           0, 5, 1, 1, HORI, NWEST,  1.0, 0.0);

 	SwingUtils.addComponent (defs, labelSCat,        1, 4, 1, 1, NONE, NWEST,  0.0, 0.0, BREATH_LEFT);
 	SwingUtils.addComponent (defs, sCategories,      1, 5, 1, 1, NONE, NWEST,  0.0, 0.0, BREATH_LEFT);

 	SwingUtils.addComponent (defs, labelSURL,        0, 6, 2, 1, NONE, NWEST,  0.0, 0.0);
 	SwingUtils.addComponent (defs, sURL,             0, 7, 2, 1, HORI, NWEST,  1.0, 0.0);
 	SwingUtils.addComponent (defs, pRDF,             0, 8, 2, 1, HORI, NWEST,  1.0, 0.0, BREATH_TOP);
 	SwingUtils.addComponent (defs, serviceType,      0, 9, 2, 1, NONE, NWEST,  0.0, 0.0, BREATH_TOP);

	// description
	sDescArea = new JTextArea();
	JPanel sDesc = createCustomTextArea ("Description", null, "serviceDesc", null, sDescArea);

	// a tree with all service types
	ServiceTypesBoard stBoard =
	    new ServiceTypesBoard (registryModel,
				   console,
				   propertyChannel,
				   new CustomServiceTypesTree2 (registryModel, console));
	stBoard.updateTree (CommonTree.SORTED_BY_NAME);

	JPanel main = createTitledPanel ("New Service");
 	SwingUtils.addComponent (main, defs,         0, 0, 2, 1, HORI, NWEST, 1.0, 0.0);
 	SwingUtils.addComponent (main, sDesc,        0, 1, 1, 1, BOTH, NWEST, 1.0, 1.0, BREATH_TOP);
 	SwingUtils.addComponent (main, stBoard,      1, 1, 1, 1, BOTH, NWEST, 1.0, 1.0, BREATH_TOP_LEFT);
 	SwingUtils.addComponent (main, copyBySelect, 0, 2, 2, 1, NONE, NWEST, 0.0, 0.0);

	// sub-panels for input/output data
	JTabbedPane dataPane = new JTabbedPane();
	dataPane.addTab ("Primary Inputs and Outputs", getPrimaryData());
	dataPane.addTab ("Secondary Inputs", getSecondaryInputs());

	// split data defs and service fields 
	JSplitPane split = hSplit (main, dataPane, 0.3);

	// buttons
	registerButton = createRegisterButton
	    (" Register Service ",
	     "Register a new service in a Biomoby registry",
	     KeyEvent.VK_S);
	showXMLButton = createShowXMLButton
	    ("Create and show XML for registering this service",
	     KeyEvent.VK_X);
	fromXMLButton = createFromXMLButton
	    ("Register this service from a raw XML in a file",
	     "registerService",
	     KeyEvent.VK_F,
	     REG_S_FROM_XML,
	     createXMLChooser (REG_S_FROM_XML));

	unregisButton =
	    createButton (" Unregister service ",
			  "Remove an existing service from a Biomoby registry",
			  KeyEvent.VK_U,
			  new ActionListener() {
			      public void actionPerformed (ActionEvent e) {
				  onUnregisterService();
			      }
			  });
	unregisButton.setIcon (unregisterIcon);
	unregisButton.setDisabledIcon (unregisterIconDis);

	agentButton =
	    createButton (" Call RDF Agent ",
			  "Ask a registry agent to come and to check your RDF document describing your services",
			  KeyEvent.VK_A,
			  new ActionListener() {
			      public void actionPerformed (ActionEvent e) {
				  onCallAgent();
			      }
			  });
	agentButton.setIcon (agentIcon);
	agentButton.setDisabledIcon (agentIconDis);

	Component glue = Box.createHorizontalGlue();

	// put it together
 	SwingUtils.addComponent (p, split,          0, 0, 6, 1, BOTH, NWEST, 1.0, 1.0);
 	SwingUtils.addComponent (p, registerButton, 0, 1, 1, 1, NONE, WEST,  0.0, 0.0);
 	SwingUtils.addComponent (p, showXMLButton,  1, 1, 1, 1, NONE, WEST,  0.0, 0.0);
 	SwingUtils.addComponent (p, fromXMLButton,  2, 1, 1, 1, NONE, WEST,  0.0, 0.0);
 	SwingUtils.addComponent (p, agentButton,    3, 1, 1, 1, NONE, WEST,  0.0, 0.0);
 	SwingUtils.addComponent (p, glue,           4, 1, 1, 1, HORI, NWEST, 1.0, 0.0);
 	SwingUtils.addComponent (p, unregisButton,  5, 1, 1, 1, NONE, NEAST, 0.0, 0.0);
	return p;
    }

    /**************************************************************************
     * Here we get notified when somebody somewhere select a service. Used to
     * update this service fields - if it is enabled by COPY_BY_SELECT field.
     **************************************************************************/
    public void propertyChange (PropertyChangeEvent event) {
	if (copyBySelect == null ||
	    ! copyBySelect.isSelected()) return;   // copy not expected/allowed
        String prop = event.getPropertyName();
        if (prop == null) return;     // no interest in non-specific changes
	Object obj = event.getNewValue();
	if (obj == null || ! (obj instanceof MobyService)) return;
        final MobyService service = (MobyService)obj;

	// finally...
	if (DP_S_SELECTED.equals (prop)) {
	    SwingUtilities.invokeLater (new Runnable() {
		    public void run() {
			onFillService (service);
		    }
		});
	}
    }

    /**************************************************************************
     *
     **************************************************************************/
    protected void onFillService (MobyService service) {
	sName.setText (service.getName());
	sAuth.setText (service.getAuthority());
	sEmail.setText (service.getEmailContact());
	sURL.setText (service.getURL());
	sSigURL.setText (service.getSignatureURL());
	sDescArea.setText (service.getDescription());
	useAuth.setEnabled (service.isAuthoritative());
	sType.setText (service.getType());
	String value = service.getPathToRDF();
	if (UUtils.notEmpty (value))
	    localRDFFile.getTextField().setText (service.getPathToRDF());	
	primInTable.setData (service.getPrimaryInputs());
	primOutTable.setData (service.getPrimaryOutputs());
 	secTable.setData (service.getSecondaryInputs());
    }

    /**************************************************************************
     *
     **************************************************************************/
    protected void onUseRDFSignature (boolean enabled) {
	sSigURL.setEnabled (enabled);
	labelSigURL.setEnabled (enabled);
	localRDFFile.setEnabled (enabled);
	labelRDFPath.setEnabled (enabled);
	setPrefValue (USE_SIGNATURE, enabled);
	propertyChannel.put (DP_USE_SIGNATURE, new Boolean (enabled).toString());
    }

    /**************************************************************************
     *
     **************************************************************************/
    protected void onAuth (boolean enabled) {
	setPrefValue (USE_AUTHORITATIVE, enabled);
	propertyChannel.put (DP_USE_AUTHORITATIVE, new Boolean (enabled).toString());
    }

    /**************************************************************************
     *
     **************************************************************************/
    protected JPanel getPrimaryData() {

	JPanel p = createTitledPanel ("");

	// data tables
	primInTable = new PrimaryDataTable();
	primOutTable = new PrimaryDataTable();

	// ...and their 'add' buttons
	JButton addButtonIn =
	    createButton (" Add input data ",
			  "Add new primary input data to the service definition",
			  KeyEvent.VK_I,
			  new ActionListener() {
			      public void actionPerformed (ActionEvent e) {
				  rbInputs.setSelected (true);
				  primInTable.addEmptyData();
			      }
			  });
	addButtonIn.setIcon (addDataIcon);
	addButtonIn.setDisabledIcon (addDataIconDis);

	JButton addButtonOut =
	    createButton (" Add output data ",
			  "Add new primary output data to the service definition",
			  KeyEvent.VK_O,
			  new ActionListener() {
			      public void actionPerformed (ActionEvent e) {
				  rbOutputs.setSelected (true);
				  primOutTable.addEmptyData();
			      }
			  });
	addButtonOut.setIcon (addDataIcon);
	addButtonOut.setDisabledIcon (addDataIconDis);

	// radio buttons to help which data table should be updated
	// from the data types and namespaces trees
	rbInputs = new JRadioButton();
	rbInputs.setSelected (true);
	rbOutputs = new JRadioButton();
	ButtonGroup group = new ButtonGroup();
	group.add (rbInputs);
	group.add (rbOutputs);

	// a tree with all already existing data types
	DataTypesBoard dataTypesBoard =
	    new DataTypesBoard (registryModel,
				console,
				propertyChannel,
				new CustomDataTypesSimplestTree (registryModel, console));
	dataTypesBoard.updateTree (CommonTree.SORTED_BY_NAME);

	// a tree with all already existing namespaces
	NamespacesBoard namespacesBoard =
	    new NamespacesBoard (registryModel,
				 console,
				 propertyChannel,
				 new CustomNamespacesSimplestTree (registryModel, console));
	namespacesBoard.updateTree (CommonTree.SORTED_BY_NAME);

	// split the trees
	JSplitPane split = hSplit (dataTypesBoard, namespacesBoard, 0.5);

	// put it together
 	SwingUtils.addComponent (p, rbInputs,                  0, 0, 1, 1, NONE, WEST,  0.0, 0.0);
 	SwingUtils.addComponent (p, addButtonIn,               1, 0, 1, 1, NONE, NWEST, 0.0, 0.0);
 	SwingUtils.addComponent (p, primInTable.scrollable(),  0, 1, 2, 1, BOTH, NWEST, 1.0, 0.3, BREATH_TOP);
 	SwingUtils.addComponent (p, rbOutputs,                 0, 2, 1, 1, NONE, WEST,  0.0, 0.0, BREATH_TOP);
 	SwingUtils.addComponent (p, addButtonOut,              1, 2, 1, 1, NONE, NWEST, 0.0, 0.0, BREATH_TOP);
 	SwingUtils.addComponent (p, primOutTable.scrollable(), 0, 3, 2, 1, BOTH, NWEST, 1.0, 0.3, BREATH_TOP);
 	SwingUtils.addComponent (p, split,                     0, 4, 2, 1, BOTH, NWEST, 1.0, 1.0, BREATH_TOP);
	return p;
    }	

    /**************************************************************************
     *
     **************************************************************************/
    protected JPanel getSecondaryInputs() {
	JPanel p = createTitledPanel ("");

	// data table
	secTable = new SecondaryDataTable();

	// ...and its 'add' button
	JButton addButtonSec =
	    createButton (" Add input data ",
			  "Add new secondary input data/parameters to the service definition",
			  KeyEvent.VK_I,
			  new ActionListener() {
			      public void actionPerformed (ActionEvent e) {
				  secTable.addEmptyData();
			      }
			  });
	addButtonSec.setIcon (addDataIcon);
	addButtonSec.setDisabledIcon (addDataIconDis);

	// put it together
 	SwingUtils.addComponent (p, addButtonSec,           0, 0, 1, 1, NONE, NWEST, 0.0, 0.0);
 	SwingUtils.addComponent (p, secTable.scrollable(),  0, 1, 1, 1, BOTH, NWEST, 1.0, 1.0, BREATH_TOP);
	return p;
    }	

    /**************************************************************************
     *
     **************************************************************************/
    public void checkAndRegister (boolean realRegistration)
	throws MobyException {

	String value = sName.getText();
	if (UUtils.isEmpty (value))
	    throw new MobyException ("Service name is still missing.\n" +
				     "Please fill it first.");

	MobyService service = new MobyService (value);
	
	value = sAuth.getText();
	if (UUtils.isEmpty (value))
	    throw new MobyException ("Every service must have an authority.\n" +
				     "Please fill it.");
	service.setAuthority (value);
	
	value = sEmail.getText();
	if (UUtils.isEmpty (value))
	    throw new MobyException ("Every service must have a contact person.\n" +
				     "Please fill in an email address.");
	service.setEmailContact (value);
	
	value = (String)sCategories.getSelectedItem();
	service.setCategory (value);
	
	value = sDescArea.getText();
	if (UUtils.isEmpty (value))
	    throw new MobyException ("Every service must have a description.\n" +
				     "Please fill in the most detailed one.");
	service.setDescription (value);
	
	value = sURL.getText();
	if (UUtils.isEmpty (value))
	    throw new MobyException ("Every service must have an endpoint (URL).\n" +
				     "Please fill in a service URL.");
	service.setURL (value);
	
	value = sType.getText();
	if (UUtils.isEmpty (value))
	    throw new MobyException ("Every service must belong to an existing service type.\n" +
				     "Please select a type from available service types.");
	service.setType (value);
	
	service.setAuthoritative (useAuth.isSelected());
	
	MobyPrimaryData[] primInputs = primInTable.getData();
	for (int i = 0; i < primInputs.length; i++) {
	    checkPrimaryData (primInputs[i]);
	    service.addInput (primInputs[i]);
	}
	MobySecondaryData[] params = secTable.getData();
	for (int i = 0; i < params.length; i++) {
	    checkSecondaryData (params[i]);
	    service.addInput (params[i]);
	}
	MobyPrimaryData[] primOutputs = primOutTable.getData();
	for (int i = 0; i < primOutputs.length; i++) {
	    checkPrimaryData (primOutputs[i]);
	    service.addOutput (primOutputs[i]);
	}
	
	if (useSignature.isSelected()) {
	    value = sSigURL.getText();
	    if (UUtils.notEmpty (value)) {
		if (! confirm ("You specified an RDF endpoint of your service.\n" +
			       "This means that you will not be able\n" +
			       "to unregister your service yourself,\n" +
			       "you will need to wait for a Biomoby 'agent'\n" +
			       "to come and fail to find your RDF document.\n \n" +
			       "While this is a legitimate and in long-run term\n" +
			       "the only correct way, you may not wish to do so\n" +
			       "if you are only testing your service and expecting\n" +
			       "still to change service attributes.\n \n" +
			       "Are you sure you wish to register the service\n" +
			       "with the RDF signature?\n \n"))
		    return;
		service.setSignatureURL (value);
		value = localRDFFile.getTextField().getText();
		if (UUtils.isEmpty (value)) {
		    try {
			value = File.createTempFile (service.getName() + "-", ".rdf").getAbsolutePath();
		    } catch (IOException e2) {
			throw new MobyException ("Problem with creating temporary file: " +
						 e2.toString());
		    }
		}
		service.setPathToRDF (value);
	    }
	}

	// ...and register it

	if (realRegistration) {
	    console.setText ("Service to be registered:\n" +
			     "-------------------------\n" +
			     service.toString());
	    registryModel.registerService (service);
	    console.setText ("\nRegistration successful!\n\n");
	    updateCache();
 	} else {
 	    String xml = registryModel.getRegisterServiceXML (service);
 	    console.setText ("\n" + xml + "\n");
	}

    }

    /**************************************************************************
     * Check validity of given data (input/output, primary/secondary).
     **************************************************************************/
    private void checkData (MobyData data)
	throws MobyException {
	String name = data.getName();
	if (UUtils.isEmpty (name))
	    throw new MobyException ("All service data must be named.\n" +
				     "Please add an article name.");
    }

    /**************************************************************************
     * Check validity of given primary data (input/output).
     **************************************************************************/
    private void checkPrimaryData (MobyPrimaryData data)
	throws MobyException {
	checkData (data);
	MobyDataType dataType = data.getDataType();
	if (dataType == null || UUtils.isEmpty (dataType.getName()))
	    throw new MobyException ("Data type is missing by datum " +
				     data.getName() +
				     "\nPlease select a data type.");
    }

    /**************************************************************************
     * Check validity of given secondary data (input).
     **************************************************************************/
    private void checkSecondaryData (MobySecondaryData data)
	throws MobyException {
	checkData (data);
	if (UUtils.isEmpty (data.getDataType()))
	    throw new MobyException ("Data type is missing by datum " +
				     data.getName() +
				     "\nPlease select a data type.");
    }

    /**************************************************************************
     *
     **************************************************************************/
    public void checkAndCallAgent()
	throws MobyException {

	// check...
	if (useSignature.isSelected()) {
	    String value = sSigURL.getText();
	    if (UUtils.isEmpty (value))
		throw new MobyException ("RDF endpoint (signature URL) is still empty.\n" +
					 "Please fill it first.");
	    MobyService service = new MobyService ("");
	    service.setSignatureURL (value);

	    // ...and call the agent
	    registryModel.registerService (service);
	    console.setText ("\nRDF Agent call was successful!\n\n");
	    updateCache();
	} else {
	    throw new MobyException ("In order to call an RDF agent, a Signature URL is needed.\n" +
				     "Please enable 'Service RDF Signature' and fill the 'RDF endpoint'.");
	}
    }

    /*********************************************************************
     *
     ********************************************************************/
    protected void onUnregisterService() {

	String name = sName.getText();
	if (UUtils.isEmpty (name)) {
	    AbstractPanel.error ("Specify name and authority of a service\n" +
				 "that you wish to be unregistered.");
	    return;
	}

	final MobyService service = new MobyService (name);
	
	String auth = sAuth.getText();
	if (UUtils.isEmpty (auth)) {
	    AbstractPanel.error ("For unregistering you also need\n" +
				 "to fill in a service authority.\n");
	    return;
	}
	service.setAuthority (auth);

	if (! confirm ("Are you sure you wish to unregister\n" +
		       "service: " + name + " ?"))
	    return;

	final SwingWorker worker = new SwingWorker() {
		MobyException exception = null;
		boolean oldAppendMode;
		public Object construct() {
		    try {
			unregisButton.setEnabled (false);
			oldAppendMode = console.setAppendMode (true);
			console.setText ("Service to be unregistered: " +
					 service.getName() + "\n");
			registryModel.unRegisterService (service);
			console.setText ("\nUnregistration successful!\n\n");
			updateCache();
			
		    } catch (MobyException e) {
			exception = e;
		    }
		    return null;  // not used here
		}
		
		// runs on the event-dispatching thread.
		public void finished() {
		    if (exception != null)
			error ("An error occured when trying to unregister a service.\n\n",
			       exception);
		    console.setAppendMode (oldAppendMode);
		    unregisButton.setEnabled (true);
		}
	    };
	worker.start(); 
    }

    /*********************************************************************
     *
     ********************************************************************/
    protected void onCallAgent() {
	agentButton.setEnabled (false);
	final SwingWorker worker = new SwingWorker() {
		boolean oldAppendMode;
		StatusBag bag;
		MobyException exception = null;
		public Object construct() {
		    try {
			checkAndCallAgent();
		    } catch (MobyException e) {
			exception = e;
		    }
		    return null;  // not used here
		}

		// runs on the event-dispatching thread.
		public void finished() {
		    if (exception != null)
			error ("An error occured when trying to call an RDF agent.\n\n",
			       exception);
		    agentButton.setEnabled (true);
		    maybeDisableVerbose (bag);
		    console.setAppendMode (oldAppendMode);
		}
	    };
	worker.start(); 
    }

    /**************************************************************************
     * This is separated here because the registration from XML is
     * done in the super-class (RegistrationPanel), but the update
     * must be done here, in sub-class.
     **************************************************************************/
    protected void updateCache()
	throws MobyException {
	registryModel.updateServicesCache();
    }

    /**************************************************************************
     *
     * Customized tree of service types - use for service registration
     *
     **************************************************************************/
    protected class CustomServiceTypesTree2
	extends ServiceTypesTree {

	/*********************************************************************
	 * Construtor
	 ********************************************************************/
	public CustomServiceTypesTree2 (RegistryModel model,
					CommonConsole console) {
	    super (model, console);
	}

	/*********************************************************************
	 *
	 ********************************************************************/
	protected void createPopups (String title) {
 	    super.createPopups (title);
 	    removeFromPopups (AC_RELOAD);
 	    removeSeparatorAfter (AC_COLLAPSE);
	}

	/*********************************************************************
	 *
	 ********************************************************************/
	protected void setEnabledPopup (boolean enabled) {
	    super.setEnabledPopup (enabled);
	    selected (null);
	}

	/*********************************************************************
	 * Copy selected service type to the service fields.
	 ********************************************************************/
	protected void selected (DefaultMutableTreeNode node) {
	    if (node == null) return;
	    CommonNode nodeObject = (CommonNode)node.getUserObject();
	    if (nodeObject.getType() == CommonNode.NODE_SERVICE_TYPE) {
		String value = nodeObject.getValue();
		sType.setText (value);
		setPrefValue (REG_S_TYPE, value);
		propertyChannel.put (DP_REG_S_TYPE, value);
	    }
	}
    }


    /**************************************************************************
     *
     * Customized tree of data types - use for service registration
     *
     **************************************************************************/
    protected class CustomDataTypesSimplestTree
	extends DataTypesTree {

	/*********************************************************************
	 * Construtor
	 ********************************************************************/
	public CustomDataTypesSimplestTree (RegistryModel model,
					    CommonConsole console) {
	    super (model, console);
	}

	/*********************************************************************
	 *
	 ********************************************************************/
	protected void createPopups (String title) {
 	    super.createPopups (title);
 	    removeFromPopups (AC_RELOAD);
 	    removeFromPopups (AC_HASA);
 	    removeFromPopups (AC_DEPR);
 	    removeSeparatorAfter (AC_ASORT);
 	    removeSeparatorAfter (AC_COLLAPSE);
	}

	/*********************************************************************
	 *
	 ********************************************************************/
	protected void setEnabledPopup (boolean enabled) {
	    super.setEnabledPopup (enabled);
	    selected (null);
	}

	/*********************************************************************
	 * Copy selected data type to the data table.
	 ********************************************************************/
	protected void selected (DefaultMutableTreeNode node) {
	    if (node == null) return;
	    CommonNode nodeObject = (CommonNode)node.getUserObject();
	    if (nodeObject.getType() == CommonNode.NODE_DATA_TYPE) {
		PrimaryDataTable table = (rbInputs.isSelected() ? primInTable : primOutTable);
		int[] rows = table.getSelectedRows();
		for (int i = 0; i < rows.length; i++) {
		    String value = nodeObject.getValue();
		    table.setValueAt (value, rows[i], PrimaryDataTable.COL_DATATYPE);
		    Object article = table.getValueAt (rows[i], PrimaryDataTable.COL_ARTICLE);
		    if (article == null || UUtils.isEmpty (article.toString()))
			table.setValueAt (value, rows[i], PrimaryDataTable.COL_ARTICLE);
		}
	    }
	}
    }

    /**************************************************************************
     *
     * Customized tree of namespaces - use for service registration
     *
     **************************************************************************/
    protected class CustomNamespacesSimplestTree
	extends NamespacesTree {

	/*********************************************************************
	 * Construtor
	 ********************************************************************/
	public CustomNamespacesSimplestTree (RegistryModel model,
					     CommonConsole console) {
	    super (model, console);
	}

	/*********************************************************************
	 *
	 ********************************************************************/
	protected void createPopups (String title) {
 	    super.createPopups (title);
 	    removeFromPopups (AC_RELOAD);
 	    removeSeparatorAfter (AC_COLLAPSE);
	}

	/*********************************************************************
	 *
	 ********************************************************************/
	protected void setEnabledPopup (boolean enabled) {
	    super.setEnabledPopup (enabled);
	    selected (null);
	}

	/*********************************************************************
	 * Copy selected namespace to the data table.
	 ********************************************************************/
	protected void selected (DefaultMutableTreeNode node) {
	    if (node == null) return;
	    CommonNode nodeObject = (CommonNode)node.getUserObject();
	    if (nodeObject.getType() == CommonNode.NODE_NAMESPACE) {
		PrimaryDataTable table = (rbInputs.isSelected() ? primInTable : primOutTable);
		int[] rows = table.getSelectedRows();
		for (int i = 0; i < rows.length; i++) {
		    String value = nodeObject.getValue();
		    MobyPrimaryData data =
			(MobyPrimaryData)table.getValueAt (rows[i],
							   PrimaryDataTable.COL_NAMESPACE);
		    data.addNamespace (new MobyNamespace (value));
		    table.setValueAt (data, rows[i], PrimaryDataTable.COL_NAMESPACE);
		}
	    }
	}
    }


}
