View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership. The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License. You may obtain a copy of the License at
9    * 
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   * 
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.river.example.browser;
19  
20  import java.awt.BorderLayout;
21  import java.awt.Dimension;
22  import java.awt.Rectangle;
23  import java.awt.event.ActionEvent;
24  import java.awt.event.ActionListener;
25  import java.awt.event.MouseAdapter;
26  import java.awt.event.MouseEvent;
27  import java.awt.event.WindowAdapter;
28  import java.awt.event.WindowEvent;
29  import java.lang.reflect.Field;
30  import java.lang.reflect.Modifier;
31  import java.net.MalformedURLException;
32  import java.rmi.server.ExportException;
33  import java.util.StringTokenizer;
34  import java.util.logging.Level;
35  import java.util.logging.Logger;
36  import javax.swing.DefaultListModel;
37  import javax.swing.JButton;
38  import javax.swing.JFrame;
39  import javax.swing.JList;
40  import javax.swing.JMenu;
41  import javax.swing.JMenuBar;
42  import javax.swing.JMenuItem;
43  import javax.swing.JOptionPane;
44  import javax.swing.JPanel;
45  import javax.swing.JPopupMenu;
46  import javax.swing.JScrollPane;
47  import javax.swing.JTree;
48  import javax.swing.SwingUtilities;
49  import javax.swing.event.PopupMenuEvent;
50  import javax.swing.event.PopupMenuListener;
51  import javax.swing.tree.TreePath;
52  import net.jini.admin.JoinAdmin;
53  import net.jini.config.ConfigurationException;
54  import net.jini.core.discovery.LookupLocator;
55  import net.jini.core.entry.Entry;
56  import net.jini.core.event.EventRegistration;
57  import net.jini.core.event.RemoteEvent;
58  import net.jini.core.event.RemoteEventListener;
59  import net.jini.core.lease.Lease;
60  import net.jini.core.lookup.ServiceEvent;
61  import net.jini.core.lookup.ServiceItem;
62  import net.jini.core.lookup.ServiceMatches;
63  import net.jini.core.lookup.ServiceRegistrar;
64  import net.jini.core.lookup.ServiceTemplate;
65  import net.jini.export.Exporter;
66  import net.jini.jeri.BasicILFactory;
67  import net.jini.jeri.BasicJeriExporter;
68  import net.jini.jeri.tcp.TcpServerEndpoint;
69  import net.jini.lookup.DiscoveryAdmin;
70  import net.jini.lookup.SafeServiceRegistrar;
71  import net.jini.lookup.entry.UIDescriptor;
72  import net.jini.lookup.ui.factory.JFrameFactory;
73  import net.jini.security.TrustVerifier;
74  import net.jini.security.proxytrust.ServerProxyTrust;
75  import org.apache.river.admin.DestroyAdmin;
76  import org.apache.river.config.Config;
77  import org.apache.river.logging.Levels;
78  import org.apache.river.proxy.BasicProxyTrustVerifier;
79  
80  /**
81   * ServiceEditor is a gui-based utility to add/modify
82   * attributes, groups and lookup locators.
83   * And also supports some well known admin interfaces.
84   * (DestroyAdmin, DiscoveryAdmin)
85   *
86   * <p>
87   * current issues<br>
88   * <ul>
89   *   <li> can't operate(add/remove/modify) array elements in an entry.
90   *   <li> does not support EntryBean.
91   *   <li> field modification is not based on EditableTree
92   * </ul>
93   *
94   * @author Sun Microsystems, Inc.
95   */
96  class ServiceEditor extends JFrame {
97    private static final Logger logger = Browser.logger;
98  
99    private Browser browser;
100   private ServiceItem item;
101   private SafeServiceRegistrar registrar;
102   protected Object admin;
103   private ServiceTemplate stmpl;
104   private NotifyReceiver receiver;
105   private Lease elease = null;
106   private long eventID = 0;
107   private long seqNo = Long.MAX_VALUE;
108   private AttributeTreePanel attrPanel;
109 
110   private final static int MINIMUM_WINDOW_WIDTH = 320;
111 
112   public ServiceEditor(ServiceItem item,
113 		       Object admin,
114 		       SafeServiceRegistrar registrar,
115 		       Browser browser)
116     {
117     super("ServiceItem Editor");
118     
119     this.item = item;
120     this.admin = admin;
121     this.registrar = registrar;
122     this.browser = browser;
123 
124     // init main components
125     attrPanel = new AttributeTreePanel();
126 
127     // setup notify
128     try {
129       stmpl = new ServiceTemplate(item.serviceID,
130 				  new Class[] { item.service.getClass() },
131 				  new Entry[] {});
132       receiver = new NotifyReceiver();
133 
134       setupNotify();
135     } catch (Throwable t) {
136       logger.log(Level.INFO, "event registration failed", t);
137       cancelNotify();
138     }
139 
140     addWindowListener(browser.wrap(new WindowAdapter() {
141 	public void windowClosing(WindowEvent e) {
142 	  cleanup();
143 	}
144       }));
145     // add menu and attr panel
146     getContentPane().setLayout(new BorderLayout());
147     getContentPane().add(new JoinMenuBar(), "North");
148     getContentPane().add(attrPanel, "Center");
149 
150     validate();
151     pack();
152     setSize(((getSize().width < MINIMUM_WINDOW_WIDTH) ? MINIMUM_WINDOW_WIDTH : getSize().width),
153 	    getSize().height);
154 
155     // center in parent frame
156     Rectangle bounds = browser.getBounds();
157     Dimension dialogSize = getPreferredSize();
158     int xpos = bounds.x + (bounds.width - dialogSize.width)/ 2;
159     int ypos = bounds.y + (bounds.height - dialogSize.height)/2;
160     setLocation((xpos < 0) ? 0 : xpos,
161 		(ypos < 0) ? 0 : ypos);
162   }
163 
164   void cleanup() {
165     // cancel lease
166     cancelNotify();
167     // release resources and close all child frames
168     dispose();
169     receiver.unexport();
170   }
171 
172   protected void cancelNotify() {
173     if(elease != null) {
174       try {
175 	browser.leaseMgr.cancel(elease);
176       } catch (Throwable t) {
177 	logger.log(Levels.HANDLED, "event cancellation failed", t);
178       }
179       elease = null;
180       seqNo = Long.MAX_VALUE;
181     }
182   }
183 
184   protected void setupNotify() {
185     if(registrar != null) {
186       try {
187 	EventRegistration reg =
188 	    registrar.notiFy(stmpl,
189 			     ServiceRegistrar.TRANSITION_MATCH_NOMATCH |
190 			     ServiceRegistrar.TRANSITION_NOMATCH_MATCH |
191 			     ServiceRegistrar.TRANSITION_MATCH_MATCH,
192 			     receiver.proxy,
193 			     null,
194 			     Lease.ANY);
195 	elease = (Lease) browser.leasePreparer.prepareProxy(reg.getLease());
196 	browser.leaseMgr.renewUntil(elease, Lease.ANY,
197 				    new Browser.LeaseNotify());
198 	eventID = reg.getID();
199 	seqNo = reg.getSequenceNumber();
200       } catch (Throwable t) {
201 	logger.log(Level.INFO, "event registration failed", t);
202       }
203     }
204   }
205 
206   private class NotifyReceiver implements RemoteEventListener, ServerProxyTrust
207   {
208     private final Exporter exporter;
209     final RemoteEventListener proxy;
210 
211     public NotifyReceiver() throws ConfigurationException, ExportException {
212       exporter = (Exporter)
213 	  Config.getNonNullEntry(browser.config, Browser.BROWSER,
214 				 "listenerExporter", Exporter.class,
215 				 new BasicJeriExporter(
216 					     TcpServerEndpoint.getInstance(0),
217 					     new BasicILFactory(),
218 					     false, false));
219       proxy = (RemoteEventListener) exporter.export(this);
220     }
221 
222     public void notify(final RemoteEvent ev) {
223       SwingUtilities.invokeLater(browser.wrap(new Runnable() {
224 	public void run() {
225 	  if (eventID == ev.getID() && seqNo < ev.getSequenceNumber()) {
226 	    seqNo = ev.getSequenceNumber();
227 	    attrPanel.receiveNotify(((ServiceEvent) ev).getTransition());
228 	  }
229 	}
230       }));
231     }
232 
233     public TrustVerifier getProxyVerifier() {
234       return new BasicProxyTrustVerifier(proxy);
235     }
236 
237     void unexport() {
238 	exporter.unexport(true);
239     }
240   }
241 
242   class JoinMenuBar extends JMenuBar {
243     public JoinMenuBar() {
244       JMenuItem mitem;
245 
246       // "File" Menu
247       JMenu fileMenu = (JMenu) add(new JMenu("File"));
248       mitem = (JMenuItem) fileMenu.add(new JMenuItem("Show Info"));
249       mitem.addActionListener(browser.wrap(new ActionListener() {
250 	public void actionPerformed(ActionEvent ev) {
251 	  Class[] infs = Browser.getInterfaces(item.service.getClass());
252 	  String[] msg = new String[3 + infs.length];
253 	  msg[0] = "ServiceID: " + item.serviceID;
254 	  msg[1] = "Service Instance: " + item.service.getClass().getName();
255 	  if(infs.length == 1)
256 	    msg[2] = "Implemented Interface:";
257 	  else
258 	    msg[2] = "Implemented Interfaces:";
259 	  for(int i = 0; i < infs.length; i++)
260 	    msg[3 + i] = "    " + infs[i].getName();
261 
262 	  JOptionPane.showMessageDialog(ServiceEditor.this,
263 					msg,
264 					"ServiceItem Information",
265 					JOptionPane.INFORMATION_MESSAGE);
266 	}
267       }));
268       mitem = (JMenuItem) fileMenu.add(new JMenuItem("Refresh"));
269       mitem.addActionListener(browser.wrap(new ActionListener() {
270 	public void actionPerformed(ActionEvent ev) {
271 	  attrPanel.refreshPanel();
272 	}
273       }));
274       mitem = (JMenuItem) fileMenu.add(new JMenuItem("Close"));
275       mitem.addActionListener(browser.wrap(new ActionListener() {
276 	public void actionPerformed(ActionEvent ev) {
277 	  cleanup();
278 	}
279       }));
280 
281       // "Edit" Menu
282       JMenu editMenu = (JMenu) add(new JMenu("Edit"));
283       mitem = (JMenuItem) editMenu.add(new JMenuItem("Add Attribute..."));
284       mitem.addActionListener(browser.wrap(new ActionListener() {
285 	public void actionPerformed(ActionEvent ev) {
286 	  attrPanel.addAttr();
287 	}
288       }));
289       if(! (admin instanceof JoinAdmin))
290 	mitem.setEnabled(false);
291       mitem = (JMenuItem) editMenu.add(new JMenuItem("Remove Attribute"));
292       mitem.addActionListener(browser.wrap(new ActionListener() {
293 	public void actionPerformed(ActionEvent ev) {
294 	  attrPanel.removeAttr();
295 	}
296       }));
297       if(! (admin instanceof JoinAdmin))
298 	mitem.setEnabled(false);
299 
300       // "Admin" Menu
301       JMenu adminMenu = (JMenu) add(new JMenu("Admin"));
302 
303       // Group (JoinAdmin)
304       mitem = (JMenuItem) adminMenu.add(new JMenuItem("Joining groups..."));
305       mitem.addActionListener(browser.wrap(new ActionListener() {
306 	public void actionPerformed(ActionEvent ev) {
307 	  new GroupLister("Joining Groups").showFrame();
308 	}
309       }));
310       if(! (admin instanceof JoinAdmin))
311 	mitem.setEnabled(false);
312 
313       // Locator (JoinAdmin)
314       mitem = (JMenuItem) adminMenu.add(new JMenuItem("Joining locators..."));
315       mitem.addActionListener(browser.wrap(new ActionListener() {
316 	public void actionPerformed(ActionEvent ev) {
317 	  new LocatorLister("Joining Locators").showFrame();
318 	}
319       }));
320       if(! (admin instanceof JoinAdmin))
321 	mitem.setEnabled(false);
322 
323       // separator
324       adminMenu.addSeparator();
325 
326       // Group (DiscoveryAdmin)
327       mitem = (JMenuItem) adminMenu.add(new JMenuItem("Member groups..."));
328       mitem.addActionListener(browser.wrap(new ActionListener() {
329 	public void actionPerformed(ActionEvent ev) {
330 	  new MemberGroupLister("Member Groups").showFrame();
331 	}
332       }));
333       if(! (admin instanceof DiscoveryAdmin))
334 	mitem.setEnabled(false);
335 
336       // Unicast port (DiscoveryAdmin)
337       mitem = (JMenuItem) adminMenu.add(new JMenuItem("Unicast port..."));
338       mitem.addActionListener(browser.wrap(new ActionListener() {
339 	public void actionPerformed(ActionEvent ev) {
340 	  try {
341 	    String[] msg = { "Current port is " + ((DiscoveryAdmin) admin).getUnicastPort(),
342 			     "Input a new value" };
343 	    String result = JOptionPane.showInputDialog(ServiceEditor.this,
344 							msg,
345 							"Unicast Port",
346 							JOptionPane.QUESTION_MESSAGE);
347 
348 	    if(result == null)
349 	      return;
350 
351 	    try {
352 	      int port = Integer.parseInt(result);
353 	      ((DiscoveryAdmin) admin).setUnicastPort(port);
354 	    } catch (NumberFormatException e) {
355 	      JOptionPane.showMessageDialog(ServiceEditor.this,
356 					    result + " is not acceptable.",
357 					    "Error",
358 					    JOptionPane.ERROR_MESSAGE);
359 	    } catch (Throwable t) {
360 	      logger.log(Level.INFO, "setting unicast port failed", t);
361 	      JOptionPane.showMessageDialog(ServiceEditor.this,
362 					    t.getMessage(),
363 					    t.getClass().getName(),
364 					    JOptionPane.ERROR_MESSAGE);
365 	    }
366 	  } catch (Throwable t) {
367 	    logger.log(Level.INFO, "getting unicast port failed", t);
368 	    JOptionPane.showMessageDialog(ServiceEditor.this,
369 					  t.getMessage(),
370 					  t.getClass().getName(),
371 					  JOptionPane.ERROR_MESSAGE);
372 	  }
373 	}
374       }));
375       if(! (admin instanceof DiscoveryAdmin))
376 	mitem.setEnabled(false);
377 
378       // separator
379       adminMenu.addSeparator();
380 
381       // DestroyAdmin
382       mitem = (JMenuItem) adminMenu.add(new JMenuItem("Destroy"));
383       mitem.addActionListener(browser.wrap(new ActionListener() {
384 	public void actionPerformed(ActionEvent ev) {
385 	  if(JOptionPane.showConfirmDialog(ServiceEditor.this,
386 					   "Are you sure to destroy this service?",
387 					   "Query",
388 					   JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION)
389 	    try {
390 	      ((DestroyAdmin) admin).destroy();
391 	      cleanup();
392 	    } catch (Throwable t) {
393 	      logger.log(Level.INFO, "service destroy failed", t);
394 	      JOptionPane.showMessageDialog(ServiceEditor.this,
395 					    t.getMessage(),
396 					    t.getClass().getName(),
397 					    JOptionPane.ERROR_MESSAGE);
398 	    }
399 	}
400       }));
401       if(! (admin instanceof DestroyAdmin))
402 	mitem.setEnabled(false);
403     }
404   }
405 
406   class AttributeTreePanel extends EntryTreePanel {
407 
408     public AttributeTreePanel() {
409       super(admin instanceof JoinAdmin);
410 
411       if(admin instanceof JoinAdmin) {
412 	tree.addMouseListener(browser.wrap(new DoubleClicker(this)));
413       }
414       tree.addMouseListener(browser.wrap(new MouseReceiver(item, uiDescriptorPopup())));
415 
416       refreshPanel();
417     }
418 
419     protected Entry[] getEntryArray() {
420       if(admin instanceof JoinAdmin) {
421 	try {
422 	  item.attributeSets = ((JoinAdmin) admin).getLookupAttributes();
423 	} catch (Throwable t) {
424 	  logger.log(Level.INFO, "obtaining attributes failed", t);
425 	}
426       } else {
427 	try{
428 	  ServiceMatches matches = registrar.lookup(stmpl, 1);
429 	  if(matches.totalMatches != 1)
430 	    Browser.logger.log(Level.INFO, "unexpected lookup matches: {0}",
431 			       Integer.valueOf(matches.totalMatches));
432 	  else
433 	    item.attributeSets = matches.items[0].attributeSets;
434 	} catch (Throwable t) {
435 	  Browser.logger.log(Level.INFO, "lookup failed", t);
436 	}
437       }
438       return item.attributeSets;
439     }
440 
441     protected void receiveNotify(int transition) {
442 
443       if (browser.isAutoConfirm()) {
444         if (transition == ServiceRegistrar.TRANSITION_MATCH_NOMATCH)
445           cleanup();
446         else
447           refreshPanel();
448 
449         return;
450       }
451 
452 	String[] msg =
453 	    (transition == ServiceRegistrar.TRANSITION_MATCH_NOMATCH) ?
454 	    new String[]{
455 		"Service has been removed from lookup service.",
456 		"Do you want to close the service editor window ?"} :
457 	    new String[]{
458 		"Attributes have been modified by another client or the service itself.",
459 		"Do you want to refresh the attributes ?"};
460       int result = JOptionPane.showConfirmDialog(AttributeTreePanel.this,
461 						 msg,
462 						 "Query",
463 						 JOptionPane.YES_NO_OPTION);
464 	
465       if(result == JOptionPane.YES_OPTION) {
466 	  if (transition == ServiceRegistrar.TRANSITION_MATCH_NOMATCH)
467 	      cleanup();
468 	  else
469 	      refreshPanel();
470       }
471     }
472 
473     public void editField(ObjectNode node) {
474 
475       String result = JOptionPane.showInputDialog(this,
476 						  "Input a new value",
477 						  "Modify a field",
478 						  JOptionPane.QUESTION_MESSAGE);
479 
480       if(result == null){
481       } else {
482 	// Save current value as template
483 	Entry template = cloneEntry((Entry) node.getEntryTop());
484 	Object oldVal = null;
485 
486 	if(result.length() == 0){
487 	  oldVal = node.setValue(null);
488 	} else {
489 	  oldVal = node.setValue(result);
490 	}
491 	// modifyAttribute
492 	try {
493 	  node.setObjectRecursive();
494 	  Entry attr = (Entry) node.getEntryTop();
495 	  //Entry template = (Entry) generateTemplate(attr);
496 
497 	  // cancel notify while adding an attribute
498 	  cancelNotify();
499 
500 	  ((JoinAdmin) admin).modifyLookupAttributes(
501 			      new Entry[] { template }, new Entry[] { attr });
502 
503 	  setupNotify();
504 
505 	  // Redraw node value
506 	  model.nodeChanged(node);
507 	} catch (Throwable t) {
508 	  logger.log(Level.INFO, "attribute modification failed", t);
509 	  // recover tree node
510 	  try {
511 	    node.setValue(oldVal);
512 	    node.setObjectRecursive();
513 	  } catch (Throwable tt) {
514 	    logger.log(Levels.HANDLED, "node reset failed", tt);
515 	  }
516 	  model.nodeChanged(node);
517 	  //model.nodeStructureChanged(node);
518 
519 	  // show dialog
520 	  JOptionPane.showMessageDialog(AttributeTreePanel.this,
521 					t.getMessage(),
522 					t.getClass().getName(),
523 					JOptionPane.ERROR_MESSAGE);
524 	}
525       }
526     }
527 
528     public void addAttr() {
529 
530       String result = JOptionPane.showInputDialog(this,
531 						  "Input an entry class name",
532 						  "Add an attribute",
533 						  JOptionPane.QUESTION_MESSAGE);
534 
535       if(result == null || result.length() == 0){
536       } else {
537 	try {
538 	  Class clazz = Class.forName(result);
539 	  Object attr = clazz.newInstance();
540 
541 	  if(! (attr instanceof Entry)){
542 	    JOptionPane.showMessageDialog(AttributeTreePanel.this,
543 					  "Does not implement Entry interface",
544 					  "Unacceptable Class",
545 					  JOptionPane.WARNING_MESSAGE);
546 
547 	  } else if(attr instanceof net.jini.lookup.entry.ServiceControlled){
548 	    JOptionPane.showMessageDialog(AttributeTreePanel.this,
549 					  "Implements ServiceControlled interface",
550 					  "Unacceptable Class",
551 					  JOptionPane.WARNING_MESSAGE);
552 	  } else {
553 	    // cancel notify while adding an attribute
554 	    cancelNotify();
555 
556 	    ((JoinAdmin) admin).addLookupAttributes(
557 						new Entry[] { (Entry) attr });
558 	    // add node of this attribute
559 	    ObjectNode node = new ObjectNode(attr, true);
560 	    root.add(node);
561 	    recursiveObjectTree(node);
562 
563 	    // 
564 	    setupNotify();
565 
566 	    // refresh view
567 	    model.nodesWereInserted(root,
568 				    new int[] { model.getIndexOfChild(root, node) });
569 	  }
570 	} catch (ClassNotFoundException e) {
571 	  JOptionPane.showMessageDialog(AttributeTreePanel.this,
572 					e.getMessage(),
573 					"Class Not Found",
574 					JOptionPane.WARNING_MESSAGE);
575 	} catch (Throwable t) {
576 	  logger.log(Level.INFO, "adding attribute failed", t);
577 	  JOptionPane.showMessageDialog(AttributeTreePanel.this,
578 					t.getMessage(),
579 					t.getClass().getName(),
580 					JOptionPane.WARNING_MESSAGE);
581 	}
582       }
583     }
584 
585     public void removeAttr() {
586 
587       ObjectNode node = (ObjectNode) tree.getLastSelectedPathComponent();
588       if(node == null){
589 	JOptionPane.showMessageDialog(AttributeTreePanel.this,
590 				      "Select an attribute folder to remove.",
591 				      "Warning",
592 				      JOptionPane.WARNING_MESSAGE);
593 	
594 	return;
595       } else if(! node.isControllable()){
596 	JOptionPane.showMessageDialog(AttributeTreePanel.this,
597 				      "This attribute is under service provider's control.",
598 				      "Warning",
599 				      JOptionPane.WARNING_MESSAGE);
600 	
601 	return;
602       } else if(! node.isEntryTop()){
603 	JOptionPane.showMessageDialog(AttributeTreePanel.this,
604 				      "Select a top of attribute folder.",
605 				      "Warning",
606 				      JOptionPane.WARNING_MESSAGE);
607 	
608 	return;
609       }
610 
611       Entry target = (Entry) node.getObject();
612       int result = JOptionPane.showConfirmDialog(AttributeTreePanel.this,
613 						 new String[] {"Remove attribute:",
614 								 target.toString() },
615 						 "Query",
616 						 JOptionPane.YES_NO_OPTION);
617 	
618       if(result == JOptionPane.YES_OPTION){
619 	// Remote Attribute
620 	try {
621 
622 	  // cancel notify while adding an attribute
623 	  cancelNotify();
624 
625 	  ((JoinAdmin) admin).modifyLookupAttributes(
626 				new Entry[] { target }, new Entry[] { null });
627 
628 	  // 
629 	  setupNotify();
630 
631 	  int index = root.getIndex(node);
632 	  root.remove(node);
633 	  model.nodesWereRemoved(root, new int[] {index}, new Object[] {node});
634 	  node = null;
635 	} catch (Throwable t) {
636 	  logger.log(Level.INFO, "attribute removal failed", t);
637 	  JOptionPane.showMessageDialog(AttributeTreePanel.this,
638 					t.getMessage(),
639 					t.getClass().getName(),
640 					JOptionPane.ERROR_MESSAGE);
641 	}
642       }
643     }
644 
645     private Entry cloneEntry(Entry attr) {
646       try {
647 	Class realClass = attr.getClass();
648 	Entry template = (Entry) realClass.newInstance();
649 
650 	Field[] f = realClass.getFields();
651 	for(int i = 0; i < f.length; i++) {
652 	  if(! usableField(f[i]))
653 	    continue;
654 	  f[i].set(template, f[i].get(attr));
655 	}
656 
657 	return template;
658       } catch (Throwable t) {
659 	logger.log(Level.INFO, "duplicating entry failed", t);
660       }
661       return null;
662     }
663 
664     // from EntryRep
665     private boolean usableField(Field field) {
666       Class desc = field.getDeclaringClass();
667 
668       if(desc.isPrimitive()) {
669 	throw new IllegalArgumentException("primitive types not allowed in an Entry");
670       }
671 
672       // skip anything that isn't a public per-object mutable field
673       int mods = field.getModifiers();
674       return (0 == (mods & (Modifier.TRANSIENT | Modifier.STATIC | Modifier.FINAL)));
675     }
676 
677     private Entry generateTemplate(Entry attr) {
678       try {
679 	Class realClass = attr.getClass();
680 	Entry template = (Entry) realClass.newInstance();
681 
682 	Field[] f = realClass.getFields();
683 	for(int i = 0; i < f.length; i++)
684 	  f[i].set(template, null);
685 
686 	return template;
687       } catch (Throwable t) {
688 	logger.log(Level.INFO, "instantiating template failed", t);
689       }
690       return null;
691     }
692 
693     class DoubleClicker extends MouseAdapter {
694       AttributeTreePanel parent;
695 
696       public DoubleClicker(AttributeTreePanel parent){
697 	this.parent = parent;
698       }
699 
700       public void mouseClicked(MouseEvent ev){
701 	if(ev.getClickCount() >= 2){
702 	  JTree tree = (JTree) ev.getSource();
703 	  TreePath path = tree.getPathForLocation(ev.getX(), ev.getY());
704 	  if(path == null)
705 	    return;
706 	  ObjectNode node = (ObjectNode) path.getLastPathComponent();
707 
708 	  if(node.isLeaf()){
709 	    if(! node.isControllable()){
710 	      JOptionPane.showMessageDialog(AttributeTreePanel.this,
711 					    "This attribute is under service provider's control.",
712 					    "Warning",
713 					    JOptionPane.WARNING_MESSAGE);
714 	    } else if(node.isEditable() &&
715 		      ((ObjectNode) node.getParent()).isEntryTop())
716 	    {
717 	      parent.editField(node);
718 	    } else {
719 	      JOptionPane.showMessageDialog(AttributeTreePanel.this,
720 					    "This field is not editable.",
721 					    "Warning",
722 					    JOptionPane.WARNING_MESSAGE);
723 	    }
724 	  }
725 
726 	  tree.scrollPathToVisible(path);
727 	}
728       }
729     }
730   }
731 
732   abstract class ListerFrame extends JFrame {
733 
734     private JList listBox;
735     private JScrollPane scrollPane;
736     protected DefaultListModel model = new DefaultListModel();
737     private DefaultListModel dummyModel = new DefaultListModel();	// to keep away from Swing's bug
738 
739     private JButton addButton;
740     private JButton removeButton;
741     private JButton closeButton;
742 
743     public ListerFrame(String title) {
744       super(title);
745 
746       getContentPane().setLayout(new BorderLayout());
747 
748       // create the initial list
749       listBox = new JList(model);
750       listBox.setFixedCellHeight(20);
751       scrollPane = new JScrollPane(listBox);
752       getContentPane().add(scrollPane, "Center");
753       //resetListModel();
754 
755       // Create the controls 
756       JPanel buttonPanel = new JPanel();
757       addButton = new JButton("Add");
758       addButton.addActionListener(browser.wrap(new ActionListener() {
759 	public void actionPerformed(ActionEvent ev) {
760 	  String result = JOptionPane.showInputDialog(ListerFrame.this, getAddMessage());
761 
762 	  if(result != null){
763 	    StringTokenizer st = new StringTokenizer(result);
764 	    String[] tokens = new String[st.countTokens()];
765 	    for(int i = 0; i < tokens.length; i++)
766 	      tokens[i] = st.nextToken().trim();
767 
768 	    addItems(tokens);
769 	    resetListModel();
770 	    scrollPane.validate();
771 	  }
772 	}
773       }));
774       buttonPanel.add(addButton);
775 
776       removeButton = new JButton("Remove");
777       removeButton.addActionListener(browser.wrap(new ActionListener() {
778 	public void actionPerformed(ActionEvent ev) {
779 	  Object[] selected = listBox.getSelectedValues();
780 
781 	  if(selected == null || selected.length == 0){
782 	    // no items are selected
783 	    JOptionPane.showMessageDialog(ListerFrame.this,
784 					  "No items are selected",
785 					  "Warning",
786 					  JOptionPane.WARNING_MESSAGE);
787 	    return;
788 	  }
789 
790 	  int result = JOptionPane.showConfirmDialog(ListerFrame.this,
791 						     getRemoveMessage(selected),
792 						     "Query",
793 						     JOptionPane.YES_NO_OPTION);
794 	
795 	  if(result == JOptionPane.YES_OPTION){
796 	    removeItems(selected);
797 	    resetListModel();
798 	    scrollPane.validate();
799 	  }
800 	}
801       }));
802       buttonPanel.add(removeButton);
803 
804       closeButton = new JButton("Close");
805       closeButton.addActionListener(browser.wrap(new ActionListener() {
806 	public void actionPerformed(ActionEvent ev) {
807 	  setVisible(false);
808 	}
809       }));
810       buttonPanel.add(closeButton);
811       getContentPane().add(buttonPanel, "South");
812 
813       pack();
814     }
815 
816     public void showFrame() {
817       // init list data
818       resetListModel();
819 
820       // center in parent frame
821       Rectangle bounds = ServiceEditor.this.getBounds();
822       Dimension dialogSize = getPreferredSize();
823 
824       setLocation(bounds.x + (bounds.width - dialogSize.width)/ 2,
825 		  bounds.y + (bounds.height - dialogSize.height)/2);
826 
827       setVisible(true);
828     }
829 
830     private void resetListModel() {
831       //listBox.setModel(null);
832       listBox.setModel(dummyModel);	// to keep away from NullException (Swing's bug)
833 
834       model.removeAllElements();
835       initListModel();
836 
837       listBox.setModel(model);
838       listBox.clearSelection();
839       listBox.ensureIndexIsVisible(0);
840       listBox.repaint();
841       listBox.revalidate();
842     }
843 
844     protected abstract void initListModel();
845 
846     protected abstract String getAddMessage();
847 
848     protected abstract String getRemoveMessage(Object[] items);
849 
850     protected abstract void addItems(String[] items);
851 
852     protected abstract void removeItems(Object[] items);
853   }
854 
855   class GroupLister extends ListerFrame {
856 
857     public GroupLister(String title) {
858       super(title);
859     }
860 
861     protected void initListModel() {
862 	if (!(admin instanceof JoinAdmin)) {
863 	    return;
864 	}
865 
866       try {
867 	String[] groups = ((JoinAdmin) admin).getLookupGroups();
868 	for(int i = 0; i < groups.length; i++) {
869 	  model.addElement(new GroupItem(groups[i]));
870 	}
871       } catch (Throwable t) {
872 	logger.log(Level.INFO, "obtaining groups failed", t);
873       }
874     }
875 
876     protected String getAddMessage() {
877       return "Enter adding group(s)";
878     }
879 
880     protected String getRemoveMessage(Object[] items) {
881       StringBuffer msg = new StringBuffer();
882       if(items.length > 1)
883 	msg.append("Remove these groups : ");
884       else
885 	msg.append("Remove a group : ");
886       for(int i = 0; i < items.length; i++) {
887 	if(i != 0)
888 	  msg.append(", ");
889 	msg.append(((GroupItem) items[i]).toString());
890       }
891       return msg.toString();
892     }
893 
894     protected void addItems(String[] items) {
895       // check "public"
896       String[] grps = new String[items.length];
897       for(int i = 0; i < items.length; i++)
898 	grps[i] = new GroupItem(items[i]).group;
899 
900       try {
901 	((JoinAdmin) admin).addLookupGroups(grps);
902       } catch (Throwable t) {
903 	logger.log(Level.INFO, "adding groups failed", t);
904       }
905     }
906 
907     protected void removeItems(Object[] items) {
908       String[] grps = new String[items.length];
909       for(int i = 0; i < items.length; i++)
910 	grps[i] = ((GroupItem) items[i]).group;
911 
912       try {
913 	((JoinAdmin) admin).removeLookupGroups(grps);
914       } catch (Throwable t) {
915 	logger.log(Level.INFO, "removing groups failed", t);
916       }
917     }
918   }
919 
920   class MemberGroupLister extends ListerFrame {
921 
922     public MemberGroupLister(String title) {
923       super(title);
924     }
925 
926     protected void initListModel() {
927       try {
928 	String[] groups = ((DiscoveryAdmin) admin).getMemberGroups();
929 	for(int i = 0; i < groups.length; i++) {
930 	  model.addElement(new GroupItem(groups[i]));
931 	}
932       } catch (Throwable t) {
933 	logger.log(Level.INFO, "obtaining groups failed", t);
934       }
935     }
936 
937     protected String getAddMessage() {
938       return "Enter adding group(s)";
939     }
940 
941     protected String getRemoveMessage(Object[] items) {
942       StringBuffer msg = new StringBuffer();
943       if(items.length > 1)
944 	msg.append("Remove these groups : ");
945       else
946 	msg.append("Remove a group : ");
947       for(int i = 0; i < items.length; i++) {
948 	if(i != 0)
949 	  msg.append(", ");
950 	msg.append(((GroupItem) items[i]).toString());
951       }
952       return msg.toString();
953     }
954 
955     protected void addItems(String[] items) {
956       // check "public"
957       String[] grps = new String[items.length];
958       for(int i = 0; i < items.length; i++)
959 	grps[i] = new GroupItem(items[i]).group;
960 
961       try {
962 	((DiscoveryAdmin) admin).addMemberGroups(grps);
963       } catch (Throwable t) {
964 	logger.log(Level.INFO, "adding groups failed", t);
965       }
966     }
967 
968     protected void removeItems(Object[] items) {
969       String[] grps = new String[items.length];
970       for(int i = 0; i < items.length; i++)
971 	grps[i] = ((GroupItem) items[i]).group;
972 
973       try {
974 	((DiscoveryAdmin) admin).removeMemberGroups(grps);
975       } catch (Throwable t){
976 	logger.log(Level.INFO, "removing groups failed", t);
977       }
978     }
979   }
980 
981   class GroupItem {
982     public String group;
983 
984     public GroupItem(String group) {
985       if(group.equals("public"))
986 	this.group = "";
987       else
988 	this.group = group;
989     }
990 
991     public String toString() {
992       if("".equals(group))
993 	return "public";
994       else
995 	return group;
996     }
997   }
998 
999   class LocatorLister extends ListerFrame {
1000 
1001     public LocatorLister(String title) {
1002       super(title);
1003     }
1004 
1005     protected void initListModel() {
1006 	if (!(admin instanceof JoinAdmin)) {
1007 	    return;
1008 	}
1009 
1010       try {
1011 	LookupLocator[] locators = ((JoinAdmin) admin).getLookupLocators();
1012 	for(int i = 0; i < locators.length; i++) {
1013 	  model.addElement(locators[i]);
1014 	}
1015       } catch (Throwable t) {
1016 	logger.log(Level.INFO, "obtaining locators failed", t);
1017       }
1018     }
1019 
1020     protected String getAddMessage() {
1021       return "Enter a new locator's URL";
1022     }
1023 
1024     protected String getRemoveMessage(Object[] items) {
1025       StringBuffer msg = new StringBuffer();
1026       if(items.length > 1)
1027 	msg.append("Remove these locators : ");
1028       else
1029 	msg.append("Remove a locator : ");
1030       for(int i = 0; i < items.length; i++) {
1031 	if(i != 0)
1032 	  msg.append(", ");
1033 	msg.append(items[i].toString());
1034       }
1035       return msg.toString();
1036     }
1037 
1038     protected void addItems(String[] items) {
1039       LookupLocator[] locs = new LookupLocator[items.length];
1040       for(int i = 0; i < items.length; i++) {
1041 	try {
1042 	  locs[i] = new LookupLocator(items[i]);
1043 	} catch (MalformedURLException e) {
1044 	  JOptionPane.showMessageDialog(LocatorLister.this,
1045 					"\"" + items[i] + "\": " +
1046 					e.getMessage(),
1047 					"Bad Locator",
1048 					JOptionPane.WARNING_MESSAGE);
1049 	  return;
1050 	}
1051       }
1052 
1053       try {
1054 	((JoinAdmin) admin).addLookupLocators(locs);
1055       } catch (Throwable t){
1056 	logger.log(Level.INFO, "adding locators failed", t);
1057       }
1058     }
1059 
1060     protected void removeItems(Object[] items) {
1061       LookupLocator[] locs = new LookupLocator[items.length];
1062       for(int i = 0; i < items.length; i++)
1063 	locs[i] = (LookupLocator) items[i];
1064 
1065       try {
1066 	((JoinAdmin) admin).removeLookupLocators(locs);
1067       } catch (Throwable t) {
1068 	logger.log(Level.INFO, "removing locators failed", t);
1069       }
1070     }
1071   }
1072 
1073     // provides support for ServiceUI
1074     public class UIDescriptorPopup extends JPopupMenu implements ActionListener,
1075 	PopupMenuListener {
1076 
1077 	protected transient JMenuItem showUIItem;
1078 	protected transient ServiceItem serviceItem;
1079 
1080 	public UIDescriptorPopup() {
1081 	    super();
1082 
1083 	    showUIItem = new JMenuItem("Show UI");
1084 
1085 	    showUIItem.addActionListener(this);
1086 	    showUIItem.setActionCommand("showUI");
1087 	    add(showUIItem);
1088 
1089 	    addPopupMenuListener(this);
1090 	    setOpaque(true);
1091 	    setLightWeightPopupEnabled(true);
1092 	}
1093 
1094 	public void actionPerformed(ActionEvent anEvent) {
1095 
1096 	    UIDescriptor uiDescriptor = getSelectedUIDescriptor();
1097 
1098 	    if (uiDescriptor == null) {
1099 		return;
1100 	    }
1101 
1102 	    try {
1103 		JFrameFactory uiFactory = (JFrameFactory)
1104 		    uiDescriptor.getUIFactory(Thread.currentThread().getContextClassLoader());
1105 		JFrame frame = uiFactory.getJFrame(serviceItem);
1106 
1107 		frame.validate();
1108 		frame.setVisible(true);
1109 	    }
1110 	    catch (Exception e) {
1111 		e.printStackTrace();
1112 
1113 		return;
1114 	    }
1115 	}
1116 
1117 	public void popupMenuWillBecomeVisible(PopupMenuEvent ev) {
1118 	}
1119 
1120 	public void popupMenuWillBecomeInvisible(PopupMenuEvent ev) {
1121 	}
1122 
1123 	public void popupMenuCanceled(PopupMenuEvent ev) {
1124 	}
1125 
1126 	public void setServiceItem(ServiceItem anItem) {
1127 	    serviceItem = anItem;
1128 	}
1129     }
1130 
1131     class MouseReceiver extends MouseAdapter {
1132 
1133 	private ServiceEditor.UIDescriptorPopup popup;
1134 	private ServiceItem serviceItem;
1135 
1136 	public MouseReceiver(ServiceItem aServiceItem,
1137 			ServiceEditor.UIDescriptorPopup popup) {
1138 		this.popup = popup;
1139 		serviceItem = aServiceItem;
1140 	}
1141 
1142 	public void mouseReleased(MouseEvent ev) {
1143 
1144 	    higlightSelection(ev);
1145 
1146 	    if (!ev.isPopupTrigger()) {
1147 		return;
1148 	    }
1149 
1150 	    UIDescriptor selectedDescriptor = getSelectedUIDescriptor();
1151 
1152 	    if (selectedDescriptor == null) {
1153 		return;
1154 	    }
1155 
1156 	    if (!"javax.swing".equals(selectedDescriptor.toolkit)) {
1157 		return;
1158 	    }
1159 
1160 	    popup.setServiceItem(serviceItem);
1161 	    popup.show(ev.getComponent(), ev.getX(), ev.getY());
1162 	}
1163 
1164 	public void mousePressed(MouseEvent ev) {
1165 
1166 	    higlightSelection(ev);
1167 
1168 	    if (!ev.isPopupTrigger()) {
1169 		return;
1170 	    }
1171 
1172 	    UIDescriptor selectedDescriptor = getSelectedUIDescriptor();
1173 
1174 	    if (selectedDescriptor == null) {
1175 		return;
1176 	    }
1177 
1178 	    if (!"javax.swing".equals(selectedDescriptor.toolkit)) {
1179 		return;
1180 	    }
1181 
1182 	    popup.setServiceItem(serviceItem);
1183 	    popup.show(ev.getComponent(), ev.getX(), ev.getY());
1184 	}
1185     }
1186 
1187     private UIDescriptor getSelectedUIDescriptor() {
1188 
1189 	ObjectNode selectedNode =
1190 		(ObjectNode) attrPanel.tree.getLastSelectedPathComponent();
1191 
1192 	if (selectedNode == null) {
1193 	    return null;
1194 	}
1195 
1196 	Object selectedObject = selectedNode.getObject();
1197 
1198 	try {
1199 	    return (UIDescriptor) selectedObject;
1200 	}
1201 	catch (ClassCastException e) {
1202 	    return null;
1203 	}
1204     }
1205 
1206     private void higlightSelection(MouseEvent event) {
1207 	attrPanel.tree.setSelectionPath(attrPanel.tree.getPathForLocation(
1208 	    event.getX(), event.getY()));
1209     }
1210 
1211     private ServiceEditor.UIDescriptorPopup uiDescriptorPopup() {
1212 	return new ServiceEditor.UIDescriptorPopup();
1213     }
1214 }