Ongoing work to add client profile and run browser inside it.
diff --git a/browser-module/pom.xml b/browser-module/pom.xml
index 63e6d94..593d5ed 100644
--- a/browser-module/pom.xml
+++ b/browser-module/pom.xml
@@ -32,16 +32,9 @@
<dependencies>
<dependency>
- <groupId>org.apache.river</groupId>
+ <groupId>org.apache.river.container</groupId>
<artifactId>browser</artifactId>
- <version>2.2.2</version>
- <scope>compile</scope>
- </dependency>
-
- <dependency>
- <groupId>org.apache.river</groupId>
- <artifactId>browser-dl</artifactId>
- <version>2.2.2</version>
+ <version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
diff --git a/browser-module/src/assemble/module.xml b/browser-module/src/assemble/module.xml
index e85ea9b..a456cfd 100644
--- a/browser-module/src/assemble/module.xml
+++ b/browser-module/src/assemble/module.xml
@@ -27,7 +27,7 @@
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
- <fileSets>
+ <fileSets>
<fileSet>
<directory>${basedir}/src/main/root</directory>
@@ -38,24 +38,17 @@
<outputDirectory>/</outputDirectory>
</fileSet>
- </fileSets>
- <dependencySets>
+ </fileSets>
+ <dependencySets>
<dependencySet>
<useProjectArtifact>false</useProjectArtifact>
<outputDirectory>/lib</outputDirectory>
<includes>
- <include>org.apache.river:browser</include>
+ <include>*:browser</include>
</includes>
</dependencySet>
- <dependencySet>
- <useProjectArtifact>false</useProjectArtifact>
- <outputDirectory>/lib-dl</outputDirectory>
- <includes>
- <include>org.apache.river:browser-dl</include>
- </includes>
- </dependencySet>
</dependencySets>
</assembly>
diff --git a/browser-module/src/main/root/browser.config b/browser-module/src/main/root/browser.config
index b5fea76..b612279 100644
--- a/browser-module/src/main/root/browser.config
+++ b/browser-module/src/main/root/browser.config
@@ -6,7 +6,7 @@
import net.jini.jeri.tcp.TcpServerEndpoint;
import net.jini.security.BasicProxyPreparer;
-com.sun.jini.example.browser {
+org.apache.river.container.examples.browser {
initialLookupGroups = new String[] {$discoveryGroup};
initialMemberGroups = new String[] {$discoveryGroup};
diff --git a/browser-module/src/main/root/start.properties b/browser-module/src/main/root/start.properties
index f2d75ce..d62b147 100644
--- a/browser-module/src/main/root/start.properties
+++ b/browser-module/src/main/root/start.properties
@@ -16,5 +16,5 @@
# limitations under the License.
#
-startClass=com.sun.jini.example.browser.Browser
+startClass=org.apache.river.container.examples.browser.Browser
startParameters=browser.config $*
diff --git a/browser/pom.xml b/browser/pom.xml
new file mode 100644
index 0000000..8870fbe
--- /dev/null
+++ b/browser/pom.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.river.container</groupId>
+ <artifactId>river-container</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ </parent>
+ <groupId>org.apache.river.container</groupId>
+ <artifactId>browser</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ <name>browser</name>
+ <url>http://maven.apache.org</url>
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ </properties>
+ <dependencies>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>3.8.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.jini</groupId>
+ <artifactId>jsk-platform</artifactId>
+ <version>2.2.1</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.jini</groupId>
+ <artifactId>jsk-lib</artifactId>
+ <version>2.2.1</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.jini</groupId>
+ <artifactId>jsk-resources</artifactId>
+ <version>2.2.1</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/browser/src/main/java/org/apache/river/container/examples/browser/App.java b/browser/src/main/java/org/apache/river/container/examples/browser/App.java
new file mode 100644
index 0000000..0f7400f
--- /dev/null
+++ b/browser/src/main/java/org/apache/river/container/examples/browser/App.java
@@ -0,0 +1,13 @@
+package org.apache.river.container.examples.browser;
+
+/**
+ * Hello world!
+ *
+ */
+public class App
+{
+ public static void main( String[] args )
+ {
+ System.out.println( "Hello World!" );
+ }
+}
diff --git a/browser/src/main/java/org/apache/river/container/examples/browser/Browser.java b/browser/src/main/java/org/apache/river/container/examples/browser/Browser.java
new file mode 100644
index 0000000..16933fb
--- /dev/null
+++ b/browser/src/main/java/org/apache/river/container/examples/browser/Browser.java
@@ -0,0 +1,1765 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.examples.browser;
+
+import com.sun.jini.admin.DestroyAdmin;
+import com.sun.jini.config.Config;
+import com.sun.jini.logging.Levels;
+import com.sun.jini.proxy.BasicProxyTrustVerifier;
+import com.sun.jini.start.LifeCycle;
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowListener;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.Modifier;
+import java.net.MalformedURLException;
+import java.rmi.server.ExportException;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.Vector;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import javax.swing.BorderFactory;
+import javax.swing.DefaultListModel;
+import javax.swing.Icon;
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JRadioButtonMenuItem;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.ListCellRenderer;
+import javax.swing.SwingUtilities;
+import javax.swing.border.TitledBorder;
+import javax.swing.event.MenuEvent;
+import javax.swing.event.MenuListener;
+import javax.swing.event.PopupMenuEvent;
+import javax.swing.event.PopupMenuListener;
+import net.jini.admin.Administrable;
+import net.jini.admin.JoinAdmin;
+import net.jini.config.Configuration;
+import net.jini.config.ConfigurationException;
+import net.jini.config.ConfigurationProvider;
+import net.jini.config.EmptyConfiguration;
+import net.jini.config.NoSuchEntryException;
+import net.jini.core.constraint.MethodConstraints;
+import net.jini.core.discovery.LookupLocator;
+import net.jini.core.entry.Entry;
+import net.jini.core.event.EventRegistration;
+import net.jini.core.event.RemoteEvent;
+import net.jini.core.event.RemoteEventListener;
+import net.jini.core.lease.Lease;
+import net.jini.core.lookup.ServiceID;
+import net.jini.core.lookup.ServiceItem;
+import net.jini.core.lookup.ServiceMatches;
+import net.jini.core.lookup.ServiceRegistrar;
+import net.jini.core.lookup.ServiceTemplate;
+import net.jini.discovery.Constants;
+import net.jini.discovery.ConstrainableLookupLocator;
+import net.jini.discovery.DiscoveryEvent;
+import net.jini.discovery.DiscoveryGroupManagement;
+import net.jini.discovery.DiscoveryListener;
+import net.jini.discovery.DiscoveryLocatorManagement;
+import net.jini.discovery.DiscoveryManagement;
+import net.jini.discovery.LookupDiscoveryManager;
+import net.jini.export.Exporter;
+import net.jini.jeri.BasicILFactory;
+import net.jini.jeri.BasicJeriExporter;
+import net.jini.jeri.tcp.TcpServerEndpoint;
+import net.jini.lease.LeaseListener;
+import net.jini.lease.LeaseRenewalEvent;
+import net.jini.lease.LeaseRenewalManager;
+import net.jini.lookup.DiscoveryAdmin;
+import net.jini.lookup.ui.factory.JFrameFactory;
+import net.jini.lookup.entry.UIDescriptor;
+import net.jini.security.BasicProxyPreparer;
+import net.jini.security.ProxyPreparer;
+import net.jini.security.Security;
+import net.jini.security.SecurityContext;
+import net.jini.security.TrustVerifier;
+import net.jini.security.proxytrust.ServerProxyTrust;
+import net.jini.space.JavaSpace05;
+import net.jini.space.JavaSpace;
+
+/*
+ * This is not great user interface design. It was a quick-and-dirty hack
+ * and an experiment in on-the-fly menu construction, and it's still
+ * here because we've never had time to do anything better.
+ */
+/**
+ * Example service browser. See the package documentation for details.
+ *
+ * @author Sun Microsystems, Inc.
+ */
+public class Browser extends JFrame {
+
+ static final String BROWSER = "org.apache.river.container.examples.browser";
+ static final Logger logger = Logger.getLogger(BROWSER);
+ private transient SecurityContext ctx;
+ private transient ClassLoader ccl;
+ transient Configuration config;
+ private transient DiscoveryGroupManagement disco;
+ private transient ServiceRegistrar lookup = null;
+ private transient Object eventSource = null;
+ private transient long eventID = 0;
+ private transient long seqNo = Long.MAX_VALUE;
+ private transient ActionListener exiter;
+ private transient ServiceTemplate tmpl;
+ private transient Listener listen;
+ private transient LookupListener adder;
+ private transient Lease elease = null;
+ transient ProxyPreparer leasePreparer;
+ transient ProxyPreparer servicePreparer;
+ transient ProxyPreparer adminPreparer;
+ private transient MethodConstraints locatorConstraints;
+ transient LeaseRenewalManager leaseMgr;
+ private transient LeaseListener lnotify;
+ private transient List ignoreInterfaces;
+ private transient JTextArea text;
+ private transient JMenu registrars;
+ private transient JCheckBoxMenuItem esuper;
+ private transient JCheckBoxMenuItem ssuper;
+ private transient JCheckBoxMenuItem sclass;
+ private transient boolean isAdmin;
+ private volatile transient boolean autoConfirm;
+ private transient JList list;
+ private transient DefaultListModel listModel;
+ private transient DefaultListModel dummyModel = new DefaultListModel();
+ private transient JScrollPane listScrollPane;
+
+ /**
+ * Creates an instance with the given action listener for the Exit menu item
+ * and the given configuration. The action listener defaults to an instance
+ * of {@link Exit Exit}. The action listener can be overridden by a
+ * configuration entry. The configuration defaults to an empty
+ * configuration.
+ *
+ * @param exiter the action listener, or <code>null</code>
+ * @param config the configuration, or <code>null</code>
+ */
+ public Browser(ActionListener exiter, Configuration config)
+ throws ConfigurationException, IOException {
+ if (exiter == null) {
+ exiter = new Exit();
+ }
+ if (config == null) {
+ config = EmptyConfiguration.INSTANCE;
+ }
+ init(exiter, config);
+ }
+
+ private void init(ActionListener exiter, Configuration config)
+ throws ConfigurationException, IOException {
+ exiter = wrap((ActionListener) Config.getNonNullEntry(config, BROWSER, "exitActionListener",
+ ActionListener.class, exiter));
+ this.exiter = exiter;
+ this.config = config;
+ ctx = Security.getContext();
+ ccl = Thread.currentThread().getContextClassLoader();
+ leaseMgr = (LeaseRenewalManager) Config.getNonNullEntry(config, BROWSER, "leaseManager",
+ LeaseRenewalManager.class,
+ new LeaseRenewalManager(config));
+ isAdmin = ((Boolean) config.getEntry(
+ BROWSER, "folderView",
+ boolean.class, Boolean.TRUE)).booleanValue();
+ leasePreparer = (ProxyPreparer) Config.getNonNullEntry(config, BROWSER, "leasePreparer",
+ ProxyPreparer.class,
+ new BasicProxyPreparer());
+ servicePreparer = (ProxyPreparer) Config.getNonNullEntry(config, BROWSER, "servicePreparer",
+ ProxyPreparer.class,
+ new BasicProxyPreparer());
+ adminPreparer = (ProxyPreparer) Config.getNonNullEntry(config, BROWSER, "adminPreparer",
+ ProxyPreparer.class,
+ new BasicProxyPreparer());
+ locatorConstraints = (MethodConstraints) config.getEntry(BROWSER, "locatorConstraints",
+ MethodConstraints.class, null);
+ ignoreInterfaces = Arrays.asList((String[]) Config.getNonNullEntry(config, BROWSER, "uninterestingInterfaces",
+ String[].class,
+ new String[]{
+ "java.io.Serializable",
+ "java.rmi.Remote",
+ "net.jini.admin.Administrable",
+ "net.jini.core.constraint.RemoteMethodControl",
+ "net.jini.id.ReferentUuid",
+ "net.jini.security.proxytrust.TrustEquivalence"}));
+ autoConfirm = ((Boolean) config.getEntry(
+ BROWSER, "autoConfirm", boolean.class,
+ Boolean.FALSE)).booleanValue();
+ listen = new Listener();
+ try {
+ DiscoveryManagement disco = (DiscoveryManagement) Config.getNonNullEntry(config, BROWSER, "discoveryManager",
+ DiscoveryManagement.class);
+ if (!(disco instanceof DiscoveryGroupManagement)) {
+ throw new ConfigurationException(
+ "discoveryManager does not "
+ + " support DiscoveryGroupManagement");
+ } else if (!(disco instanceof DiscoveryLocatorManagement)) {
+ throw new ConfigurationException(
+ "discoveryManager does not "
+ + " support DiscoveryLocatorManagement");
+ }
+ this.disco = (DiscoveryGroupManagement) disco;
+ String[] groups = this.disco.getGroups();
+ if (groups == null || groups.length > 0) {
+ throw new ConfigurationException(
+ "discoveryManager cannot have initial groups");
+ }
+ if (((DiscoveryLocatorManagement) disco).getLocators().length > 0) {
+ throw new ConfigurationException(
+ "discoveryManager cannot have initial locators");
+ }
+ } catch (NoSuchEntryException e) {
+ disco = new LookupDiscoveryManager(new String[0],
+ new LookupLocator[0], null,
+ config);
+ }
+ disco.setGroups((String[]) config.getEntry(BROWSER,
+ "initialLookupGroups",
+ String[].class,
+ null));
+ ((DiscoveryLocatorManagement) disco).setLocators((LookupLocator[]) Config.getNonNullEntry(config, BROWSER, "initialLookupLocators",
+ LookupLocator[].class,
+ new LookupLocator[0]));
+ tmpl = new ServiceTemplate(null, new Class[0], new Entry[0]);
+ setTitle("Service Browser");
+ JMenuBar bar = new JMenuBar();
+ JMenu file = new JMenu("File");
+ JMenuItem allfind = new JMenuItem("Find All");
+ allfind.addActionListener(wrap(new AllFind()));
+ file.add(allfind);
+ JMenuItem pubfind = new JMenuItem("Find Public");
+ pubfind.addActionListener(wrap(new PubFind()));
+ file.add(pubfind);
+ JMenuItem multifind = new JMenuItem("Find By Group...");
+ multifind.addActionListener(wrap(new MultiFind()));
+ file.add(multifind);
+ JMenuItem unifind = new JMenuItem("Find By Address...");
+ unifind.addActionListener(wrap(new UniFind()));
+ file.add(unifind);
+ if (!isAdmin) {
+ JMenuItem show = new JMenuItem("Show Matches");
+ show.addActionListener(wrap(new Show()));
+ file.add(show);
+ }
+ JMenuItem reset = new JMenuItem("Reset");
+ reset.addActionListener(wrap(new Reset()));
+ file.add(reset);
+ JMenuItem exit = new JMenuItem("Exit");
+ exit.addActionListener(exiter);
+ file.add(exit);
+ bar.add(file);
+ addWindowListener(new Exiter());
+ registrars = new JMenu("Registrar");
+ addNone(registrars);
+ bar.add(registrars);
+ JMenu options = new JMenu("Options");
+ esuper = new JCheckBoxMenuItem("Attribute supertypes", false);
+ options.add(esuper);
+ ssuper = new JCheckBoxMenuItem("Service supertypes", false);
+ options.add(ssuper);
+ sclass = new JCheckBoxMenuItem("Service classes", false);
+ options.add(sclass);
+ bar.add(options);
+ JMenu services = new JMenu("Services");
+ services.addMenuListener(wrap(new Services(services)));
+ bar.add(services);
+ JMenu attrs = new JMenu("Attributes");
+ attrs.addMenuListener(wrap(new Entries(attrs)));
+ bar.add(attrs);
+ setJMenuBar(bar);
+
+ getContentPane().setLayout(new BorderLayout());
+ int textRows = 8;
+ if (isAdmin) {
+ textRows = 4;
+ JPanel bpanel = new JPanel();
+ bpanel.setLayout(new BorderLayout());
+
+ TitledBorder border = BorderFactory.createTitledBorder("Matching Services");
+ border.setTitlePosition(TitledBorder.TOP);
+ border.setTitleJustification(TitledBorder.LEFT);
+ bpanel.setBorder(border);
+
+ listModel = new DefaultListModel();
+ list = new JList(listModel);
+ list.setFixedCellHeight(20);
+ list.setCellRenderer((ListCellRenderer) wrap(new ServiceItemRenderer(), ListCellRenderer.class));
+ list.addMouseListener(
+ wrap(new MouseReceiver(new ServiceListPopup())));
+ listScrollPane = new JScrollPane(list);
+ bpanel.add(listScrollPane, "Center");
+ getContentPane().add(bpanel, "South");
+ }
+ text = new JTextArea(genText(false), textRows, 40);
+ text.setEditable(false);
+ JScrollPane scroll = new JScrollPane(text);
+ getContentPane().add(scroll, "Center");
+
+ validate();
+ SwingUtilities.invokeLater(wrap(new Runnable() {
+ public void run() {
+ pack();
+ show();
+ }
+ }));
+ adder = new LookupListener();
+ lnotify = new LeaseNotify();
+ ((DiscoveryManagement) disco).addDiscoveryListener(adder);
+ }
+
+ /**
+ * Creates an instance with the given command line arguments and life cycle
+ * callback. See the package documentation for details of the command line
+ * arguments. The default action listener for the Exit menu item calls the
+ * {@link #dispose dispose} method of this instance, cancels any lookup
+ * service event registration lease, unexports any remote event listener,
+ * and calls the {@link LifeCycle#unregister unregister} method of the life
+ * cycle callback. The action listener can be overridden by a configuration
+ * entry.
+ *
+ * @param args command line arguments
+ * @param lc life cycle callback, or <code>null</code>.
+ */
+ public Browser(String[] args, final LifeCycle lc)
+ throws ConfigurationException, LoginException, IOException {
+ final ActionListener exiter = new ActionListener() {
+ public void actionPerformed(ActionEvent ev) {
+ Browser.this.dispose();
+ cancelLease();
+ listen.unexport();
+ if (lc != null) {
+ lc.unregister(Browser.this);
+ }
+ System.exit(0);
+ }
+ };
+ final Configuration config =
+ ConfigurationProvider.getInstance(
+ args, Browser.class.getClassLoader());
+ LoginContext login =
+ (LoginContext) config.getEntry(BROWSER, "loginContext",
+ LoginContext.class, null);
+ if (login == null) {
+ init(exiter, config);
+ } else {
+ login.login();
+ try {
+ Subject.doAsPrivileged(
+ login.getSubject(),
+ new PrivilegedExceptionAction() {
+ public Object run()
+ throws ConfigurationException, IOException {
+ init(exiter, config);
+ return null;
+ }
+ },
+ null);
+ } catch (PrivilegedActionException pae) {
+ Exception e = pae.getException();
+ if (e instanceof ConfigurationException) {
+ throw (ConfigurationException) e;
+ }
+ throw (IOException) e;
+ }
+ }
+ }
+
+ private static String typeName(Class type) {
+ String name = type.getName();
+ int i = name.lastIndexOf('.');
+ if (i >= 0) {
+ name = name.substring(i + 1);
+ }
+ return name;
+ }
+
+ private void setText(boolean match) {
+ text.setText(genText(match));
+ }
+
+ private String genText(boolean match) {
+ StringBuffer buf = new StringBuffer();
+ if (tmpl.serviceTypes.length > 0) {
+ for (int i = 0; i < tmpl.serviceTypes.length; i++) {
+ buf.append(tmpl.serviceTypes[i].getName());
+ buf.append("\n");
+ }
+ }
+ if (tmpl.attributeSetTemplates.length > 0) {
+ genEntries(buf, tmpl.attributeSetTemplates, false);
+ }
+ genMatches(buf, match);
+ return buf.toString();
+ }
+
+ private void genEntries(StringBuffer buf,
+ Entry[] entries,
+ boolean showNulls) {
+ for (int i = 0; i < entries.length; i++) {
+ Entry ent = entries[i];
+ if (ent == null) {
+ buf.append("null\n");
+ continue;
+ }
+ buf.append(typeName(ent.getClass()));
+ buf.append(": ");
+ try {
+ Field[] fields = ent.getClass().getFields();
+ for (int j = 0; j < fields.length; j++) {
+ if (!valid(fields[j])) {
+ continue;
+ }
+ Object val = fields[j].get(ent);
+ if (val != null || showNulls) {
+ buf.append(fields[j].getName());
+ buf.append("=");
+ buf.append(val);
+ buf.append(" ");
+ }
+ }
+ } catch (Exception e) {
+ }
+ buf.append("\n");
+ }
+ }
+
+ private static boolean valid(Field f) {
+ return (f.getModifiers() & (Modifier.STATIC | Modifier.FINAL)) == 0;
+ }
+
+ private void genMatches(StringBuffer buf, boolean match) {
+ if (isAdmin) {
+ list.setModel(dummyModel); // to keep away from Swing's bug
+
+ listModel.removeAllElements();
+ list.clearSelection();
+ list.ensureIndexIsVisible(0);
+ list.repaint();
+ list.revalidate();
+ listScrollPane.validate();
+ }
+ if (lookup == null) {
+ String[] groups = disco.getGroups();
+ if (groups == null) {
+ buf.append("Groups: <all>\n");
+ } else if (groups.length > 0) {
+ buf.append("Groups:");
+ for (int i = 0; i < groups.length; i++) {
+ String group = groups[i];
+ if (group.length() == 0) {
+ group = "public";
+ }
+ buf.append(" ");
+ buf.append(group);
+ }
+ buf.append("\n");
+ }
+ LookupLocator[] locators =
+ ((DiscoveryLocatorManagement) disco).getLocators();
+ if (locators.length > 0) {
+ buf.append("Addresses:");
+ for (int i = 0; i < locators.length; i++) {
+ buf.append(" ");
+ buf.append(locators[i].getHost());
+ if (locators[i].getPort() != Constants.discoveryPort) {
+ buf.append(":");
+ buf.append(locators[i].getPort());
+ }
+ }
+ buf.append("\n");
+ }
+ if (!(registrars.getMenuComponent(0) instanceof JRadioButtonMenuItem)) {
+ buf.append("No registrars to select");
+ return;
+ }
+ int n = registrars.getMenuComponentCount();
+ if (n == 1) {
+ buf.append("1 registrar, not selected");
+ } else {
+ buf.append(n);
+ buf.append(" registrars, none selected");
+ }
+ return;
+ }
+ ServiceMatches matches;
+ try {
+ matches = lookup.lookup(tmpl, (match || isAdmin) ? 1000 : 0);
+ } catch (Throwable t) {
+ logger.log(Level.INFO, "lookup failed", t);
+ return;
+ }
+
+ if (matches.items != null) {
+ for (int i = 0; i < matches.items.length; i++) {
+ if (matches.items[i].service != null) {
+ try {
+ matches.items[i].service =
+ servicePreparer.prepareProxy(
+ matches.items[i].service);
+ } catch (Throwable t) {
+ logger.log(Level.INFO, "proxy preparation failed", t);
+ matches.items[i].service = null;
+ }
+ }
+ }
+ }
+
+ if (isAdmin) {
+ for (int i = 0; i < matches.items.length; i++) {
+ listModel.addElement(new ServiceListItem(matches.items[i]));
+ }
+ list.setModel(listModel);
+ list.clearSelection();
+ list.ensureIndexIsVisible(0);
+ list.repaint();
+ list.revalidate();
+ listScrollPane.validate();
+ }
+
+ if (!match
+ && tmpl.serviceTypes.length == 0
+ && tmpl.attributeSetTemplates.length == 0) {
+ buf.append("Total services registered: ");
+ buf.append(matches.totalMatches);
+ return;
+ }
+ buf.append("\nMatching services: ");
+ buf.append(matches.totalMatches);
+
+ if (!isAdmin) {
+ if (!match) {
+ return;
+ }
+ buf.append("\n\n");
+ for (int i = 0; i < matches.items.length; i++) {
+ ServiceItem item = matches.items[i];
+ buf.append("Service ID: ");
+ buf.append(item.serviceID);
+ buf.append("\n");
+ buf.append("Service instance: ");
+ buf.append(item.service);
+ buf.append("\n");
+ genEntries(buf, item.attributeSets, true);
+ buf.append("\n");
+ }
+ }
+ }
+
+ private static void addNone(JMenu menu) {
+ JMenuItem item = new JMenuItem("(none)");
+ item.setEnabled(false);
+ menu.add(item);
+ }
+
+ private void addOne(ServiceRegistrar registrar) {
+ LookupLocator loc;
+ try {
+ loc = registrar.getLocator();
+ } catch (Throwable t) {
+ logger.log(Level.INFO, "obtaining locator failed", t);
+ return;
+ }
+ String host = loc.getHost();
+ if (loc.getPort() != Constants.discoveryPort) {
+ host += ":" + loc.getPort();
+ }
+ JRadioButtonMenuItem reg =
+ new RegistrarMenuItem(host, registrar.getServiceID());
+ reg.addActionListener(wrap(new Lookup(registrar)));
+ if (!(registrars.getMenuComponent(0) instanceof JRadioButtonMenuItem)) {
+ registrars.removeAll();
+ }
+ registrars.add(reg);
+ }
+
+ private static class RegistrarMenuItem extends JRadioButtonMenuItem {
+
+ ServiceID id;
+
+ RegistrarMenuItem(String host, ServiceID id) {
+ super(host);
+ this.id = id;
+ }
+ }
+
+ static Class[] getInterfaces(Class c) {
+ Set set = new HashSet();
+ for (; c != null; c = c.getSuperclass()) {
+ Class[] ifs = c.getInterfaces();
+ for (int i = ifs.length; --i >= 0;) {
+ set.add(ifs[i]);
+ }
+ }
+ return (Class[]) set.toArray(new Class[set.size()]);
+ }
+
+ private class Services implements MenuListener {
+
+ private JMenu menu;
+
+ public Services(JMenu menu) {
+ this.menu = menu;
+ }
+
+ public void menuSelected(MenuEvent ev) {
+ if (lookup == null) {
+ addNone(menu);
+ return;
+ }
+ Vector all = new Vector();
+ Class[] types = tmpl.serviceTypes;
+ for (int i = 0; i < types.length; i++) {
+ all.addElement(types[i]);
+ JCheckBoxMenuItem item =
+ new JCheckBoxMenuItem(types[i].getName(), true);
+ item.addActionListener(wrap(new Service(types[i], i)));
+ menu.add(item);
+ }
+ try {
+ types = lookup.getServiceTypes(tmpl, "");
+ } catch (Throwable t) {
+ failure(t);
+ return;
+ }
+ if (types == null) {
+ if (all.isEmpty()) {
+ addNone(menu);
+ }
+ return;
+ }
+ for (int i = 0; i < types.length; i++) {
+ Class[] stypes;
+ if (types[i] == null) {
+ all.addElement(new JMenuItem("null"));
+ continue;
+ }
+ if (types[i].isInterface() || sclass.getState()) {
+ stypes = new Class[]{types[i]};
+ } else {
+ stypes = getInterfaces(types[i]);
+ }
+ for (int j = 0; j < stypes.length; j++) {
+ addType(stypes[j], all);
+ }
+ }
+ }
+
+ private void addType(Class type, Vector all) {
+ if (all.contains(type)) {
+ return;
+ }
+ all.addElement(type);
+ JCheckBoxMenuItem item =
+ new JCheckBoxMenuItem(type.getName(), false);
+ item.addActionListener(wrap(new Service(type, -1)));
+ menu.add(item);
+ if (!ssuper.getState()) {
+ return;
+ }
+ if (sclass.getState() && type.getSuperclass() != null) {
+ addType(type.getSuperclass(), all);
+ }
+ Class[] stypes = type.getInterfaces();
+ for (int i = 0; i < stypes.length; i++) {
+ addType(stypes[i], all);
+ }
+ }
+
+ public void menuDeselected(MenuEvent ev) {
+ menu.removeAll();
+ }
+
+ public void menuCanceled(MenuEvent ev) {
+ menu.removeAll();
+ }
+ }
+
+ /**
+ * Indicates whether auto confirm is enabled to prevent from the user having
+ * to click the 'Yes' button in the a popup window to confirm a modification
+ * to the service browser pane is allowed to take place as result of a
+ * service being removed, or its lookup attributes being changed.
+ *
+ * @return <code>true</code> in case no popup is required to have the user
+ * confirm the modifications, <code>false</code> otherwise
+ */
+ boolean isAutoConfirm() {
+ return autoConfirm;
+ }
+
+ ActionListener wrap(ActionListener l) {
+ return (ActionListener) wrap((Object) l, ActionListener.class);
+ }
+
+ MenuListener wrap(MenuListener l) {
+ return (MenuListener) wrap((Object) l, MenuListener.class);
+ }
+
+ MouseListener wrap(MouseListener l) {
+ return (MouseListener) wrap((Object) l, MouseListener.class);
+ }
+
+ WindowListener wrap(WindowListener a) {
+ return (WindowListener) wrap((Object) a, WindowListener.class);
+ }
+
+ Runnable wrap(Runnable r) {
+ return (Runnable) wrap((Object) r, Runnable.class);
+ }
+
+ private Object wrap(Object obj, Class iface) {
+ return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
+ new Class[]{iface}, new Handler(obj));
+ }
+
+ private class Handler implements InvocationHandler {
+
+ private final Object obj;
+
+ Handler(Object obj) {
+ this.obj = obj;
+ }
+
+ public Object invoke(Object proxy,
+ final Method method,
+ final Object[] args)
+ throws Throwable {
+ if (method.getDeclaringClass() == Object.class) {
+ if ("equals".equals(method.getName())) {
+ return Boolean.valueOf(proxy == args[0]);
+ } else if ("hashCode".equals(method.getName())) {
+ return new Integer(System.identityHashCode(proxy));
+ }
+ }
+ try {
+ return AccessController.doPrivileged(
+ ctx.wrap(new PrivilegedExceptionAction() {
+ public Object run() throws Exception {
+ Thread t = Thread.currentThread();
+ ClassLoader occl = t.getContextClassLoader();
+ try {
+ t.setContextClassLoader(ccl);
+ try {
+ return method.invoke(obj, args);
+ } catch (InvocationTargetException e) {
+ Throwable tt = e.getCause();
+ if (tt instanceof Error) {
+ throw (Error) tt;
+ }
+ throw (Exception) tt;
+ }
+ } finally {
+ t.setContextClassLoader(occl);
+ }
+ }
+ }), ctx.getAccessControlContext());
+ } catch (PrivilegedActionException e) {
+ throw e.getCause();
+ }
+ }
+ }
+
+ private class Show implements ActionListener {
+
+ public void actionPerformed(ActionEvent ev) {
+ setText(true);
+ }
+ }
+
+ private void resetTmpl() {
+ tmpl.serviceTypes = new Class[0];
+ tmpl.attributeSetTemplates = new Entry[0];
+ update();
+ }
+
+ private void reset() {
+ ssuper.setState(false);
+ esuper.setState(false);
+ sclass.setState(false);
+ resetTmpl();
+ }
+
+ private class Reset implements ActionListener {
+
+ public void actionPerformed(ActionEvent ev) {
+ reset();
+ }
+ }
+
+ private class Service implements ActionListener {
+
+ private Class type;
+ private int index;
+
+ public Service(Class type, int index) {
+ this.type = type;
+ this.index = index;
+ }
+
+ public void actionPerformed(ActionEvent ev) {
+ int z = tmpl.serviceTypes.length;
+ Class[] newTypes;
+ if (index < 0) {
+ newTypes = new Class[z + 1];
+ System.arraycopy(tmpl.serviceTypes, 0, newTypes, 0, z);
+ newTypes[z] = type;
+ } else {
+ newTypes = new Class[z - 1];
+ System.arraycopy(tmpl.serviceTypes, 0,
+ newTypes, 0, index);
+ System.arraycopy(tmpl.serviceTypes, index + 1,
+ newTypes, index, z - index - 1);
+ }
+ tmpl.serviceTypes = newTypes;
+ update();
+ }
+ }
+
+ private class Entries implements MenuListener {
+
+ private JMenu menu;
+
+ public Entries(JMenu menu) {
+ this.menu = menu;
+ }
+
+ public void menuSelected(MenuEvent ev) {
+ if (lookup == null) {
+ addNone(menu);
+ return;
+ }
+ Entry[] attrs = tmpl.attributeSetTemplates;
+ for (int i = 0; i < attrs.length; i++) {
+ Class type = attrs[i].getClass();
+ JMenu item = new JMenu(typeName(type));
+ item.addMenuListener(new Fields(item, i));
+ menu.add(item);
+ }
+ Class[] types;
+ try {
+ types = lookup.getEntryClasses(tmpl);
+ } catch (Throwable t) {
+ failure(t);
+ return;
+ }
+ if (types == null) {
+ if (attrs.length == 0) {
+ addNone(menu);
+ }
+ return;
+ }
+ Vector all = new Vector();
+ for (int i = 0; i < types.length; i++) {
+ if (types[i] == null) {
+ menu.add(new JMenuItem("null"));
+ } else {
+ addType(types[i], all);
+ }
+ }
+ }
+
+ private void addType(Class type, Vector all) {
+ if (all.contains(type)) {
+ return;
+ }
+ all.addElement(type);
+ JCheckBoxMenuItem item =
+ new JCheckBoxMenuItem(typeName(type), false);
+ item.addActionListener(wrap(new AttrSet(type)));
+ menu.add(item);
+ if (esuper.getState()
+ && Entry.class.isAssignableFrom(type.getSuperclass())) {
+ addType(type.getSuperclass(), all);
+ }
+ }
+
+ public void menuDeselected(MenuEvent ev) {
+ menu.removeAll();
+ }
+
+ public void menuCanceled(MenuEvent ev) {
+ menu.removeAll();
+ }
+ }
+
+ private class AttrSet implements ActionListener {
+
+ private Class type;
+
+ public AttrSet(Class type) {
+ this.type = type;
+ }
+
+ public void actionPerformed(ActionEvent ev) {
+ Entry ent;
+ try {
+ ent = (Entry) type.newInstance();
+ } catch (Throwable t) {
+ logger.log(Level.INFO, "creating entry failed", t);
+ return;
+ }
+ int z = tmpl.attributeSetTemplates.length;
+ Entry[] newSets = new Entry[z + 1];
+ System.arraycopy(tmpl.attributeSetTemplates, 0, newSets, 0, z);
+ newSets[z] = ent;
+ tmpl.attributeSetTemplates = newSets;
+ update();
+ }
+ }
+
+ private class Fields implements MenuListener {
+
+ private JMenu menu;
+ private int index;
+
+ public Fields(JMenu menu, int index) {
+ this.menu = menu;
+ this.index = index;
+ }
+
+ public void menuSelected(MenuEvent ev) {
+ JRadioButtonMenuItem match = new JRadioButtonMenuItem("(match)");
+ match.setSelected(true);
+ match.addActionListener(wrap(new Unmatch(index)));
+ menu.add(match);
+ Entry ent = tmpl.attributeSetTemplates[index];
+ Field[] fields = ent.getClass().getFields();
+ for (int i = 0; i < fields.length; i++) {
+ Field field = fields[i];
+ if (!valid(field)) {
+ continue;
+ }
+ try {
+ if (field.get(ent) != null) {
+ JCheckBoxMenuItem item =
+ new JCheckBoxMenuItem(field.getName(), true);
+ item.addActionListener(
+ wrap(new Value(index, field, null)));
+ menu.add(item);
+ } else {
+ JMenu item = new JMenu(field.getName());
+ item.addMenuListener(
+ wrap(new Values(item, index, field)));
+ menu.add(item);
+ }
+ } catch (Throwable t) {
+ logger.log(Level.INFO, "getting fields failed", t);
+ }
+ }
+ }
+
+ public void menuDeselected(MenuEvent ev) {
+ menu.removeAll();
+ }
+
+ public void menuCanceled(MenuEvent ev) {
+ menu.removeAll();
+ }
+ }
+
+ private class Unmatch implements ActionListener {
+
+ private int index;
+
+ public Unmatch(int index) {
+ this.index = index;
+ }
+
+ public void actionPerformed(ActionEvent ev) {
+ int z = tmpl.attributeSetTemplates.length;
+ Entry[] newSets = new Entry[z - 1];
+ System.arraycopy(tmpl.attributeSetTemplates, 0,
+ newSets, 0, index);
+ System.arraycopy(tmpl.attributeSetTemplates, index + 1,
+ newSets, index, z - index - 1);
+ tmpl.attributeSetTemplates = newSets;
+ update();
+ }
+ }
+
+ private class Values implements MenuListener {
+
+ private JMenu menu;
+ private int index;
+ private Field field;
+
+ public Values(JMenu menu, int index, Field field) {
+ this.menu = menu;
+ this.index = index;
+ this.field = field;
+ }
+
+ public void menuSelected(MenuEvent ev) {
+ Object[] values;
+ try {
+ values = lookup.getFieldValues(tmpl, index, field.getName());
+ } catch (Throwable t) {
+ failure(t);
+ return;
+ }
+ if (values == null) {
+ addNone(menu);
+ return;
+ }
+ for (int i = 0; i < values.length; i++) {
+ JMenuItem item = new JMenuItem(values[i].toString());
+ item.addActionListener(
+ wrap(new Value(index, field, values[i])));
+ menu.add(item);
+ }
+ }
+
+ public void menuDeselected(MenuEvent ev) {
+ menu.removeAll();
+ }
+
+ public void menuCanceled(MenuEvent ev) {
+ menu.removeAll();
+ }
+ }
+
+ private class Value implements ActionListener {
+
+ private int index;
+ private Field field;
+ private Object value;
+
+ public Value(int index, Field field, Object value) {
+ this.index = index;
+ this.field = field;
+ this.value = value;
+ }
+
+ public void actionPerformed(ActionEvent ev) {
+ try {
+ field.set(tmpl.attributeSetTemplates[index], value);
+ } catch (Throwable t) {
+ logger.log(Level.INFO, "setting attribute value failed", t);
+ }
+ update();
+ }
+ }
+
+ private class Listener implements RemoteEventListener, ServerProxyTrust {
+
+ private final Exporter exporter;
+ final RemoteEventListener proxy;
+
+ public Listener() throws ConfigurationException, ExportException {
+ exporter = (Exporter) Config.getNonNullEntry(config, BROWSER, "listenerExporter",
+ Exporter.class,
+ new BasicJeriExporter(
+ TcpServerEndpoint.getInstance(0),
+ new BasicILFactory(),
+ false, false));
+ proxy = (RemoteEventListener) exporter.export(this);
+ }
+
+ public void notify(final RemoteEvent ev) {
+ SwingUtilities.invokeLater(wrap(new Runnable() {
+ public void run() {
+ if (eventID == ev.getID()
+ && seqNo < ev.getSequenceNumber()
+ && eventSource != null
+ && eventSource.equals(ev.getSource())) {
+ seqNo = ev.getSequenceNumber();
+ setText(false);
+ }
+ }
+ }));
+ }
+
+ public TrustVerifier getProxyVerifier() {
+ return new BasicProxyTrustVerifier(proxy);
+ }
+
+ void unexport() {
+ exporter.unexport(true);
+ }
+ }
+
+ private class LookupListener implements DiscoveryListener {
+
+ public void discovered(DiscoveryEvent e) {
+ final ServiceRegistrar[] newregs = e.getRegistrars();
+ SwingUtilities.invokeLater(wrap(new Runnable() {
+ public void run() {
+ for (int i = 0; i < newregs.length; i++) {
+ addOne(newregs[i]);
+ }
+ if (lookup == null) {
+ setText(false);
+ }
+ }
+ }));
+ }
+
+ public void discarded(DiscoveryEvent e) {
+ final ServiceRegistrar[] regs = e.getRegistrars();
+ SwingUtilities.invokeLater(wrap(new Runnable() {
+ public void run() {
+ for (int i = 0; i < regs.length; i++) {
+ ServiceID id = regs[i].getServiceID();
+ if (lookup != null
+ && id.equals(lookup.getServiceID())) {
+ lookup = null;
+ seqNo = Long.MAX_VALUE;
+ }
+ for (int j = 0;
+ j < registrars.getMenuComponentCount();
+ j++) {
+ JMenuItem item =
+ (JMenuItem) registrars.getMenuComponent(j);
+ if (item instanceof RegistrarMenuItem
+ && id.equals(((RegistrarMenuItem) item).id)) {
+ item.setSelected(false);
+ registrars.remove(item);
+ if (registrars.getMenuComponentCount() == 0) {
+ addNone(registrars);
+ }
+ break;
+ }
+ }
+ }
+ if (lookup == null) {
+ resetTmpl();
+ }
+ }
+ }));
+ }
+ }
+
+ private void setGroups(String[] groups) {
+ ((DiscoveryLocatorManagement) disco).setLocators(new LookupLocator[0]);
+ try {
+ disco.setGroups(groups);
+ } catch (Throwable t) {
+ logger.log(Level.INFO, "setting groups failed", t);
+ }
+ resetTmpl();
+ }
+
+ private class AllFind implements ActionListener {
+
+ public void actionPerformed(ActionEvent ev) {
+ setGroups(null);
+ }
+ }
+
+ private class PubFind implements ActionListener {
+
+ public void actionPerformed(ActionEvent ev) {
+ setGroups(new String[]{""});
+ }
+ }
+
+ private class MultiFind implements ActionListener {
+
+ public void actionPerformed(ActionEvent ev) {
+ String names = JOptionPane.showInputDialog(Browser.this,
+ "Enter group names");
+ if (names == null) {
+ return;
+ }
+ setGroups(parseList(names, true));
+ }
+ }
+
+ private class UniFind implements ActionListener {
+
+ public void actionPerformed(ActionEvent ev) {
+ String list =
+ JOptionPane.showInputDialog(Browser.this,
+ "Enter host[:port] addresses");
+ if (list == null) {
+ return;
+ }
+ String[] addrs = parseList(list, false);
+ LookupLocator[] locs = new LookupLocator[addrs.length];
+ for (int i = 0; i < addrs.length; i++) {
+ try {
+ locs[i] = new ConstrainableLookupLocator(
+ "jini://" + addrs[i], locatorConstraints);
+ } catch (MalformedURLException e) {
+ JOptionPane.showMessageDialog(Browser.this,
+ "\"" + addrs[i] + "\": "
+ + e.getMessage(),
+ "Bad Address",
+ JOptionPane.ERROR_MESSAGE);
+ return;
+ }
+ }
+ try {
+ disco.setGroups(new String[0]);
+ } catch (Throwable t) {
+ logger.log(Levels.HANDLED, "setting groups failed", t);
+ }
+ ((DiscoveryLocatorManagement) disco).setLocators(locs);
+ resetTmpl();
+ }
+ }
+
+ private class Lookup implements ActionListener {
+
+ private ServiceRegistrar registrar;
+
+ public Lookup(ServiceRegistrar registrar) {
+ this.registrar = registrar;
+ }
+
+ public void actionPerformed(ActionEvent ev) {
+ if (lookup == registrar) {
+ lookup = null;
+ } else {
+ lookup = registrar;
+ }
+ seqNo = Long.MAX_VALUE;
+ for (int i = 0; i < registrars.getMenuComponentCount(); i++) {
+ JMenuItem item = (JMenuItem) registrars.getMenuComponent(i);
+ if (item != ev.getSource()) {
+ item.setSelected(false);
+ }
+ }
+ resetTmpl();
+ }
+ }
+
+ static class LeaseNotify implements LeaseListener {
+
+ public void notify(LeaseRenewalEvent ev) {
+ if (ev.getException() != null) {
+ logger.log(Level.INFO, "lease renewal failed",
+ ev.getException());
+ } else {
+ logger.log(Level.INFO, "lease renewal failed");
+ }
+ }
+ }
+
+ private static String[] parseList(String names, boolean groups) {
+ StringTokenizer st = new StringTokenizer(names, " \t\n\r\f,");
+ String[] elts = new String[st.countTokens()];
+ for (int i = 0; st.hasMoreTokens(); i++) {
+ elts[i] = st.nextToken();
+ if (groups && elts[i].equalsIgnoreCase("public")) {
+ elts[i] = "";
+ }
+ }
+ return elts;
+ }
+
+ private void cancelLease() {
+ if (elease != null) {
+ try {
+ leaseMgr.cancel(elease);
+ } catch (Throwable t) {
+ logger.log(Levels.HANDLED, "lease cancellation failed", t);
+ }
+ elease = null;
+ eventSource = null;
+ }
+ }
+
+ private void update() {
+ setText(false);
+ cancelLease();
+ if (lookup == null) {
+ return;
+ }
+ try {
+ EventRegistration reg =
+ lookup.notify(tmpl,
+ ServiceRegistrar.TRANSITION_MATCH_NOMATCH
+ | ServiceRegistrar.TRANSITION_NOMATCH_MATCH
+ | ServiceRegistrar.TRANSITION_MATCH_MATCH,
+ listen.proxy, null, Lease.ANY);
+ elease = (Lease) leasePreparer.prepareProxy(reg.getLease());
+ leaseMgr.renewUntil(elease, Lease.ANY, lnotify);
+ eventSource = reg.getSource();
+ eventID = reg.getID();
+ seqNo = reg.getSequenceNumber();
+ } catch (Throwable t) {
+ failure(t);
+ }
+ }
+
+ private void failure(Throwable t) {
+ logger.log(Level.INFO, "call to lookup service failed", t);
+ ((DiscoveryManagement) disco).discard(lookup);
+ }
+
+ class ServiceItemRenderer implements ListCellRenderer {
+
+ private JLabel label;
+
+ public ServiceItemRenderer() {
+ label = new JLabel();
+ label.setOpaque(true);
+ }
+
+ public Component getListCellRendererComponent(JList list,
+ Object value,
+ int index,
+ boolean isSelected,
+ boolean cellHasFocus) {
+ ServiceListItem item = null;
+ if (value instanceof ServiceListItem) {
+ item = (ServiceListItem) value;
+ }
+
+ label.setFont(list.getFont());
+
+ if (isSelected) {
+ label.setBackground(list.getSelectionBackground());
+ label.setForeground(list.getSelectionForeground());
+ } else {
+ label.setBackground(list.getBackground());
+ label.setForeground(list.getForeground());
+ }
+ if (item != null) {
+ // accessible check done in this method
+ label.setIcon(item.getIcon());
+ label.setText(item.getTitle());
+ } else {
+ label.setText(value.toString());
+ }
+
+ return label;
+ }
+ }
+ private static Icon[] icons = new Icon[3];
+
+ static {
+ // Administrable Service, Controllable Attribute
+ icons[0] = MetalIcons.getBlueFolderIcon();
+ // Non-administrable Service
+ icons[1] = MetalIcons.getGrayFolderIcon();
+ // "Connection Refused" Service
+ icons[2] = MetalIcons.getUnusableFolderIcon();
+ }
+
+ private class ServiceListItem {
+
+ private ServiceItem item;
+ private boolean isAccessible;
+ private Object admin = null;
+
+ public ServiceListItem(ServiceItem item) {
+ this.item = item;
+ isAccessible = (item.service != null);
+ }
+
+ public String getTitle() {
+ if (item.service == null) {
+ return "Unknown service";
+ }
+
+ HashSet set = new HashSet();
+ Class[] infs = getInterfaces(item.service.getClass());
+ for (int j = 0; j < infs.length; j++) {
+ set.add(infs[j].getName());
+ }
+
+ // remove known interfaces
+ set.removeAll(ignoreInterfaces);
+
+ String title = null;
+ if (set.size() == 1) {
+ Iterator iter = set.iterator();
+ title = (String) iter.next();
+ } else {
+ title = item.service.getClass().getName();
+ title += " [";
+ for (Iterator iter = set.iterator(); iter.hasNext();) {
+ title += (String) iter.next();
+ if (iter.hasNext()) {
+ title += ", ";
+ }
+ }
+ title += "]";
+ }
+ if (!isAccessible) {
+ title += " (Stale service)";
+ }
+ return title;
+ }
+
+ public boolean isAccessible() {
+ getAdmin();
+ return isAccessible;
+ }
+
+ public Object getAdmin() {
+ if (admin == null
+ && isAccessible
+ && item.service instanceof Administrable) {
+ try {
+ admin = adminPreparer.prepareProxy(
+ ((Administrable) item.service).getAdmin());
+ } catch (Throwable t) {
+ logger.log(Levels.HANDLED, "failed to get admin proxy", t);
+ isAccessible = false;
+ }
+ }
+ return admin;
+ }
+
+ public boolean isAdministrable() {
+ getAdmin();
+ return (admin instanceof DestroyAdmin
+ || admin instanceof JoinAdmin
+ || admin instanceof DiscoveryAdmin);
+ }
+
+ public boolean isSpaceBrowsable() {
+ return false;
+ }
+
+ private boolean isUI() {
+ Entry[] attrs = item.attributeSets;
+ if ((attrs != null) && (attrs.length != 0)) {
+ for (int i = 0; i < attrs.length; i++) {
+ if (attrs[i] instanceof UIDescriptor) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ public ServiceItem getServiceItem() {
+ return item;
+ }
+
+ public Entry[] getAttributes() {
+ return item.attributeSets;
+ }
+
+ public Icon getIcon() {
+ if (!isAccessible()) {
+ return icons[2];
+ } else if (isAdministrable()) {
+ return icons[0];
+ } else {
+ return icons[1];
+ }
+ }
+
+ public String toString() {
+ return isAccessible()
+ ? item.service.getClass().getName() : "Unknown service";
+ }
+ }
+
+ private class MouseReceiver extends MouseAdapter {
+
+ private ServiceListPopup popup;
+
+ public MouseReceiver(ServiceListPopup popup) {
+ this.popup = popup;
+ }
+
+ public void mouseClicked(MouseEvent ev) {
+ if (ev.getClickCount() >= 2) {
+ ServiceListItem listItem = getTargetListItem(ev);
+ if (listItem != null) {
+ ServiceItem item = listItem.getServiceItem();
+ if (listItem.isAdministrable()) {
+ new ServiceEditor(item, listItem.getAdmin(), lookup,
+ Browser.this).setVisible(true);
+ } else if (listItem.isAccessible()) {
+ new ServiceBrowser(item, lookup,
+ Browser.this).setVisible(true);
+ }
+ }
+ }
+ }
+
+ public void mouseReleased(MouseEvent ev) {
+ if (ev.isPopupTrigger() && (getTargetListItem(ev) != null)) {
+ popup.setServiceItem(getTargetListItem(ev));
+ popup.show(ev.getComponent(), ev.getX(), ev.getY());
+ }
+ }
+
+ public void mousePressed(MouseEvent ev) {
+ if (ev.isPopupTrigger() && (getTargetListItem(ev) != null)) {
+ popup.setServiceItem(getTargetListItem(ev));
+ popup.show(ev.getComponent(), ev.getX(), ev.getY());
+ }
+ }
+
+ private ServiceListItem getTargetListItem(MouseEvent ev) {
+ int index = list.locationToIndex(ev.getPoint());
+ if (index >= 0) {
+ return (ServiceListItem) listModel.getElementAt(index);
+ } else {
+ return null;
+ }
+ }
+ }
+
+ private class ServiceListPopup
+ extends JPopupMenu
+ implements ActionListener, PopupMenuListener {
+
+ protected JMenuItem infoItem;
+ protected JMenuItem browseItem;
+ protected JMenuItem adminItem;
+ protected JMenuItem spaceItem;
+ protected ServiceListItem listItem;
+ protected JMenuItem uiItem;
+ protected ServiceItem item;
+
+ public ServiceListPopup() {
+ super();
+
+ ActionListener me = wrap(this);
+ infoItem = new JMenuItem("Show Info");
+ infoItem.addActionListener(me);
+ infoItem.setActionCommand("showInfo");
+ add(infoItem);
+
+ browseItem = new JMenuItem("Browse Service");
+ browseItem.addActionListener(me);
+ browseItem.setActionCommand("browseService");
+ add(browseItem);
+
+ adminItem = new JMenuItem("Admin Service");
+ adminItem.addActionListener(me);
+ adminItem.setActionCommand("adminService");
+ add(adminItem);
+
+ spaceItem = new JMenuItem("Browse Entries");
+ spaceItem.addActionListener(me);
+ spaceItem.setActionCommand("browseEntry");
+ add(spaceItem);
+
+ uiItem = new JMenuItem("Show UI");
+ uiItem.addActionListener(me);
+ uiItem.setActionCommand("showUI");
+ add(uiItem);
+
+ addPopupMenuListener(this);
+ setOpaque(true);
+ setLightWeightPopupEnabled(true);
+ }
+
+ public void setServiceItem(ServiceListItem listItem) {
+ this.listItem = listItem;
+ item = listItem.getServiceItem();
+ infoItem.setEnabled(listItem.isAccessible());
+ browseItem.setEnabled(listItem.isAccessible());
+ adminItem.setEnabled(listItem.isAdministrable());
+ spaceItem.setEnabled(listItem.isSpaceBrowsable());
+ uiItem.setEnabled(listItem.isUI());
+ }
+
+ public void actionPerformed(ActionEvent ev) {
+ String command = ev.getActionCommand();
+
+ if (command.equals("showInfo")) {
+ Class[] infs = getInterfaces(item.service.getClass());
+ String[] msg = new String[3 + infs.length];
+ msg[0] = "ServiceID: " + item.serviceID;
+ msg[1] = ("Service Instance: "
+ + item.service.getClass().getName());
+ if (infs.length == 1) {
+ msg[2] = "Implemented Interface:";
+ } else {
+ msg[2] = "Implemented Interfaces:";
+ }
+ for (int i = 0; i < infs.length; i++) {
+ msg[3 + i] = infs[i].getName();
+ }
+
+ JOptionPane.showMessageDialog(Browser.this,
+ msg,
+ "ServiceItem Information",
+ JOptionPane.INFORMATION_MESSAGE);
+ } else if (command.equals("browseService")) {
+ new ServiceBrowser(item, lookup,
+ Browser.this).setVisible(true);
+ } else if (command.equals("adminService")) {
+ new ServiceEditor(item, listItem.getAdmin(),
+ lookup, Browser.this).setVisible(true);
+ } else if (command.equals("browseEntry")) {
+ // Not supported at the present time.
+ } else if (command.equals("showUI")) {
+ UIDescriptor uiDescriptor = getSelectedUIDescriptor();
+
+ if (uiDescriptor == null) {
+ return;
+ }
+
+ try {
+ JFrameFactory uiFactory = (JFrameFactory) uiDescriptor.getUIFactory(
+ Thread.currentThread().getContextClassLoader());
+ JFrame frame = uiFactory.getJFrame(item);
+
+ frame.validate();
+ frame.setVisible(true);
+ } catch (Exception e) {
+ logger.log(Level.INFO, "show ui failed", e);
+ JOptionPane.showMessageDialog(Browser.this,
+ e.getMessage(),
+ e.getClass().getName(),
+ JOptionPane.WARNING_MESSAGE);
+ return;
+ }
+ }
+ }
+
+ public void popupMenuWillBecomeVisible(PopupMenuEvent ev) {
+ }
+
+ public void popupMenuWillBecomeInvisible(PopupMenuEvent ev) {
+ }
+
+ public void popupMenuCanceled(PopupMenuEvent ev) {
+ }
+
+ private UIDescriptor getSelectedUIDescriptor() {
+
+ if (!(new ServiceListItem(item)).isUI()) {
+ return null;
+ }
+
+ Entry[] attrs = item.attributeSets;
+ if ((attrs != null) && (attrs.length != 0)) {
+ for (int i = 0; i < attrs.length; i++) {
+ if (attrs[i] instanceof UIDescriptor) {
+ UIDescriptor desc = (UIDescriptor) attrs[i];
+ if (!"javax.swing".equals(desc.toolkit)) {
+ continue;
+ }
+ return desc;
+ }
+ }
+ }
+ return null;
+ }
+ }
+
+ private class Exiter extends WindowAdapter {
+
+ public void windowClosing(WindowEvent e) {
+ Browser.this.exiter.actionPerformed(
+ new ActionEvent(e.getSource(), e.getID(),
+ "Exit", System.currentTimeMillis(), 0));
+ }
+ }
+
+ /**
+ * An action listener that cancels any lookup service event registration
+ * lease and then calls {@link System#exit System.exit}.
+ */
+ public static class Exit implements ActionListener {
+
+ /**
+ * Cancels any lookup service event registration lease and calls {@link System#exit System.exit}
+ * <code>(0)</code>.
+ */
+ public void actionPerformed(ActionEvent ev) {
+ Object src = ev.getSource();
+ while (!(src instanceof Browser) && src instanceof Component) {
+ if (src instanceof JPopupMenu) {
+ src = ((JPopupMenu) src).getInvoker();
+ } else {
+ src = ((Component) src).getParent();
+ }
+ }
+ if (src instanceof Browser) {
+ try {
+ ((Browser) src).cancelLease();
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+ }
+ System.exit(0);
+ }
+ }
+
+ /**
+ * Runs the service browser. See the package documentation for details.
+ *
+ * @param args command line arguments
+ */
+ public static void main(String[] args) {
+ if (System.getSecurityManager() == null) {
+ System.setSecurityManager(new SecurityManager());
+ }
+ try {
+ final Configuration config =
+ ConfigurationProvider.getInstance(
+ args, Browser.class.getClassLoader());
+ LoginContext login =
+ (LoginContext) config.getEntry(BROWSER, "loginContext",
+ LoginContext.class, null);
+ if (login != null) {
+ login.login();
+ }
+ PrivilegedExceptionAction action =
+ new PrivilegedExceptionAction() {
+ public Object run() throws Exception {
+ return new Browser(null, config);
+ }
+ };
+ if (login != null) {
+ Subject.doAsPrivileged(login.getSubject(), action, null);
+ } else {
+ action.run();
+ }
+ } catch (Throwable t) {
+ if (t instanceof PrivilegedActionException) {
+ t = t.getCause();
+ }
+ logger.log(Level.SEVERE, "browser initialization failed", t);
+ }
+ }
+}
diff --git a/browser/src/main/java/org/apache/river/container/examples/browser/EntryTreePanel.java b/browser/src/main/java/org/apache/river/container/examples/browser/EntryTreePanel.java
new file mode 100644
index 0000000..95ff9f6
--- /dev/null
+++ b/browser/src/main/java/org/apache/river/container/examples/browser/EntryTreePanel.java
@@ -0,0 +1,292 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.examples.browser;
+
+import net.jini.core.entry.Entry;
+import java.lang.reflect.Field;
+import java.lang.reflect.Array;
+import java.awt.Component;
+import java.awt.BorderLayout;
+import java.util.logging.Level;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTree;
+import javax.swing.JLabel;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.TreeCellRenderer;
+import javax.swing.plaf.metal.MetalLookAndFeel;
+
+/**
+ *
+ * @author Sun Microsystems, Inc.
+ *
+ * @version 0.2 06/04/98
+ *
+ */
+abstract class EntryTreePanel extends JPanel {
+ /**
+ * running mode.
+ *
+ * @serial
+ */
+ private boolean isControllable;
+
+
+ /**
+ * @serial
+ */
+
+ private JScrollPane scrollPane;
+
+ /**
+ * @serial
+ */
+ protected JTree tree;
+
+ /**
+ * @serial
+ */
+ protected ObjectNode root;
+
+ /**
+ * @serial
+ */
+ protected DefaultTreeModel model;
+
+ /**
+ * @serial
+ */
+ private boolean showModifier = false;
+
+ /**
+ * @serial
+ */
+ private boolean showPackage = false;
+
+
+ public EntryTreePanel(boolean isControllable) {
+ this.isControllable = isControllable;
+
+ // Init this panel
+ setLayout(new BorderLayout());
+
+ // Init tree node and model (attribute tree nodes)
+ root = new ObjectNode(isControllable);
+ //initTree();
+ model = new DefaultTreeModel(root);
+
+ // Init tree view
+ tree = new JTree(model);
+ //tree.addMouseListener(new DoubleClicker(this));
+ tree.setRootVisible(false);
+ ObjectNodeRenderer renderer = new ObjectNodeRenderer();
+ tree.setCellRenderer(renderer);
+ tree.setRowHeight(0); // let the renderer handle it
+ scrollPane = new JScrollPane(tree);
+ add(scrollPane, "Center");
+
+ tree.validate();
+ scrollPane.validate();
+ }
+
+ protected abstract Entry[] getEntryArray();
+
+
+ protected void initTree() {
+ Entry[] entries = getEntryArray();
+ if(entries == null)
+ entries = new Entry[0];
+
+ for(int i = 0; i < entries.length; i++){
+ // check controllability
+ boolean nodeControllable = false;
+ if(isControllable && ! (entries[i] instanceof net.jini.lookup.entry.ServiceControlled)) {
+ nodeControllable = true;
+ }
+
+ ObjectNode node = new ObjectNode(entries[i], nodeControllable);
+ root.add(node);
+ try {
+ recursiveObjectTree(node);
+ } catch(IllegalAccessException e){
+ Browser.logger.log(Level.INFO, "entry access failed", e);
+ }
+ }
+ }
+
+ public void refreshPanel() {
+ // reconstruct nodes
+ root.removeAllChildren();
+ initTree();
+
+ model.nodeStructureChanged(root);
+
+ tree.validate();
+ scrollPane.validate();
+ }
+
+ protected void recursiveObjectTree(ObjectNode node)
+ throws IllegalArgumentException, IllegalAccessException {
+
+ Object obj = node.getObject();
+ if(obj == null)
+ return;
+ //Field[] fields = obj.getClass().getDeclaredFields();
+ Field[] fields = obj.getClass().getFields();
+
+ for(int i = 0; i < fields.length; i++){
+ Field f = fields[i];
+
+ if(Introspector.isHidden(f))
+ continue;
+
+ Class clazz = f.getType();
+ ObjectNode child = null;
+ String fname = f.getName();
+ if(clazz.isPrimitive()){
+ String clazzName = clazz.toString();
+ Object fobj = null;
+ if("int".equals(clazzName)){
+ fobj = new Integer(f.getInt(obj));
+ } else if("boolean".equals(clazzName)){
+ fobj = new Boolean(f.getBoolean(obj));
+ } else if("byte".equals(clazzName)){
+ fobj = new Byte(f.getByte(obj));
+ } else if("char".equals(clazzName)){
+ fobj = new Character(f.getChar(obj));
+ } else if("double".equals(clazzName)){
+ fobj = new Double(f.getDouble(obj));
+ } else if("float".equals(clazzName)){
+ fobj = new Float(f.getFloat(obj));
+ } else if("long".equals(clazzName)){
+ fobj = new Long(f.getLong(obj));
+ }
+
+ child = new ObjectNode(fobj, clazz, fname, true);
+ } else if(Introspector.isWrapper(clazz) || Introspector.isString(clazz)) {
+ child = new ObjectNode(f.get(obj), clazz, fname, true);
+ } else if(clazz.isArray()){
+ child = new ObjectNode(f.get(obj), clazz, fname, false);
+ child.setAdministrable(node.isAdministrable());
+ child.setControllable(node.isControllable());
+ recursiveArrayTree(child, f);
+ } else {
+ // unknown type
+ Object subobj = f.get(obj);
+
+ // check if sub object has a viewable members.
+ if(countViewableFields(clazz) > 0){
+ child = new ObjectNode(subobj, clazz, fname, false);
+ child.setAdministrable(node.isAdministrable());
+ child.setControllable(node.isControllable());
+ recursiveObjectTree(child);
+ } else {
+ child = new ObjectNode(subobj, clazz, fname, true);
+ }
+ }
+ node.add(child);
+ }
+ }
+
+ private int countViewableFields(Class clazz) {
+
+ int count = 0;
+ //Field[] fields = obj.getClass().getDeclaredFields();
+ //Field[] fields = obj.getClass().getFields();
+ Field[] fields = clazz.getFields();
+ for(int i = 0; i < fields.length; i++){
+ Field f = fields[i];
+
+ if(Introspector.isHidden(f))
+ continue;
+
+ count++;
+ }
+
+ return count;
+ }
+
+ private void recursiveArrayTree(ObjectNode node, Field f)
+ throws IllegalArgumentException, IllegalAccessException {
+
+ String name = node.getFieldName();
+ Object aobj = node.getObject();
+
+ int length = Array.getLength(aobj);
+
+ Class clazz = f.getType().getComponentType();
+
+ if(clazz.isPrimitive() || Introspector.isWrapper(clazz) || Introspector.isString(clazz)){
+ // primitive, wrapper objects, string array
+ for(int i = 0; i < length; i++){
+ Object elem = Array.get(aobj, i);
+ //String fname = name + "[" + i + "]";
+ ObjectNode child = new ObjectNode(elem, clazz, name, i, true);
+ node.add(child);
+ }
+ } else {
+ // Object or Array (*sigh*)
+ for(int i = 0; i < length; i++){
+ Object elem = Array.get(aobj, i);
+ //String fname = name + "[" + i + "]";
+ ObjectNode child = new ObjectNode(elem, clazz, name, i, false);
+ recursiveObjectTree(child);
+ child.setAdministrable(node.isAdministrable());
+ child.setControllable(node.isControllable());
+
+ node.add(child);
+ }
+ }
+ }
+
+
+ class ObjectNodeRenderer implements TreeCellRenderer {
+ private JLabel label;
+
+ public ObjectNodeRenderer() {
+ label = new JLabel();
+ label.setOpaque(true);
+ }
+
+ public Component getTreeCellRendererComponent(JTree tree,
+ Object value,
+ boolean isSelected,
+ boolean isExpanded,
+ boolean isLeaf,
+ int row,
+ boolean cellHasFocus){
+
+ //label.setFont(tree.getFont());
+ label.setForeground(tree.getForeground());
+ if(isSelected){
+ //label.setBackground(UIManager.getColor("Tree.backgroundSelectionColor"));
+ //label.setForeground(UIManager.getColor("Tree.textSelectionColor"));
+ label.setBackground(MetalLookAndFeel.getPrimaryControl());
+ } else {
+ //label.setBackground(UIManager.getColor("Tree.backgroundNonSelectionColor"));
+ //label.setForeground(UIManager.getColor("Tree.textNonSelectionColor"));
+ label.setBackground(tree.getBackground());
+ }
+
+ ObjectNode node = (ObjectNode) value;
+ label.setText(node.getTitle());
+ label.setIcon(node.getIcon());
+ return label;
+ }
+ }
+}
diff --git a/browser/src/main/java/org/apache/river/container/examples/browser/Introspector.java b/browser/src/main/java/org/apache/river/container/examples/browser/Introspector.java
new file mode 100644
index 0000000..e7781d5
--- /dev/null
+++ b/browser/src/main/java/org/apache/river/container/examples/browser/Introspector.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.examples.browser;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Array;
+import java.lang.reflect.Modifier;
+
+/**
+ * Introspection related methods
+ *
+ * @author Sun Microsystems, Inc.
+ *
+ * @version 0.2 06/04/98
+ *
+ */
+
+class Introspector {
+
+ public static boolean isHidden(Field f){
+ int m = f.getModifiers();
+
+ if(Modifier.isPrivate(m) || Modifier.isStatic(m))
+ return true;
+
+ return false;
+ }
+
+ public static boolean isString(Class clazz){
+ if("java.lang.String".equals(clazz.getName()))
+ return true;
+ return false;
+ }
+
+ public static boolean isWrapper(Class clazz){
+ String name = clazz.getName();
+
+ if("java.lang.Integer".equals(name) ||
+ "java.lang.Boolean".equals(name) ||
+ "java.lang.Byte".equals(name) ||
+ "java.lang.Char".equals(name) ||
+ "java.lang.Double".equals(name) ||
+ "java.lang.Float".equals(name) ||
+ "java.lang.Long".equals(name)){
+ return true;
+ }
+ return false;
+ }
+
+ /** Return the name of an interface or primitive type, handling arrays. */
+ public static String getTypename(Class t, boolean showPackage) {
+ String brackets = "";
+ while(t.isArray()) {
+ brackets += "[]";
+ t = t.getComponentType();
+ }
+
+ if(showPackage)
+ return t.getName() + brackets;
+ else
+ return extractClassName(t.getName()) + brackets;
+ }
+
+ /** Return a string version of modifiers, handling spaces nicely. */
+ public static String getModifierString(int m) {
+ if(m == 0)
+ return "";
+ else
+ return Modifier.toString(m) + " ";
+ }
+
+ /** Print the modifiers, type, and name of a field. */
+ public static String getFieldString(Field f, boolean showModifier, boolean showPackage) {
+ String fstring = "";
+
+ if(showModifier)
+ fstring += getModifierString(f.getModifiers());
+
+ fstring += getTypename(f.getType(), showPackage);
+ fstring += " ";
+ fstring += f.getName();
+
+ return fstring;
+ }
+
+ public static String extractClassName(String fullName){
+ int index = fullName.lastIndexOf(".");
+
+ return fullName.substring(index + 1);
+ }
+}
diff --git a/browser/src/main/java/org/apache/river/container/examples/browser/MetalIcons.java b/browser/src/main/java/org/apache/river/container/examples/browser/MetalIcons.java
new file mode 100644
index 0000000..30987bb
--- /dev/null
+++ b/browser/src/main/java/org/apache/river/container/examples/browser/MetalIcons.java
@@ -0,0 +1,348 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.examples.browser;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import javax.swing.plaf.metal.MetalLookAndFeel;
+import java.io.Serializable;
+import javax.swing.Icon;
+
+/**
+ * Based on "MetalIconFactory.java"
+ *
+ * @author Sun Microsystems, Inc.
+ *
+ */
+class MetalIcons implements Serializable {
+
+ private static Icon blueFolderIcon;
+ private static Icon blueFileIcon;
+ private static Icon orangeFolderIcon;
+ private static Icon orangeFileIcon;
+ private static Icon grayFolderIcon;
+ private static Icon grayFileIcon;
+ private static Icon unusableFolderIcon;
+ private static Icon unusableFileIcon;
+
+ // Colors for Orange icons
+ private static Color orangeDarkShadowColor = new Color(0xcc, 0x99, 0x66);
+ private static Color orangeColor = new Color(0xff, 0x99, 0x33);
+ private static Color orangeShadowColor = new Color(0xcc, 0x99, 0x66);
+ private static Color orangeInfoColor = new Color(0x99, 0x00, 0x33);
+ private static Color orangeHighlightColor = new Color(0xff, 0xff, 0x33);
+
+ // Colors for Gray icons
+ private static Color grayDarkShadowColor = new Color(0x99, 0x99, 0x99);
+ private static Color grayColor = new Color(0xbb, 0xbb, 0xbb);
+ private static Color grayShadowColor = new Color(0xaa, 0xaa, 0xaa);
+ private static Color grayInfoColor = new Color(0x88, 0x88, 0x88);
+ private static Color grayHighlightColor = new Color(0xdd, 0xdd, 0xdd);
+
+ // Constants
+ public static final boolean DARK = false;
+ public static final boolean LIGHT = true;
+
+ // Accessor functions for Icons. Does the caching work.
+ public static Icon getBlueFolderIcon() {
+ if(blueFolderIcon == null)
+ blueFolderIcon = new BlueFolderIcon();
+ return blueFolderIcon;
+ }
+
+ public static Icon getBlueFileIcon() {
+ if(blueFileIcon == null)
+ blueFileIcon = new BlueFileIcon();
+ return blueFileIcon;
+ }
+
+ public static Icon getOrangeFolderIcon() {
+ if(orangeFolderIcon == null)
+ orangeFolderIcon = new OrangeFolderIcon();
+ return orangeFolderIcon;
+ }
+
+ public static Icon getOrangeFileIcon() {
+ if(orangeFileIcon == null)
+ orangeFileIcon = new OrangeFileIcon();
+ return orangeFileIcon;
+ }
+
+ public static Icon getGrayFolderIcon() {
+ if(grayFolderIcon == null)
+ grayFolderIcon = new GrayFolderIcon();
+ return grayFolderIcon;
+ }
+
+ public static Icon getGrayFileIcon() {
+ if(grayFileIcon == null)
+ grayFileIcon = new GrayFileIcon();
+ return grayFileIcon;
+ }
+
+ public static Icon getUnusableFolderIcon() {
+ if(unusableFolderIcon == null)
+ unusableFolderIcon = new UnusableFolderIcon();
+ return unusableFolderIcon;
+ }
+
+ public static Icon getUnusableFileIcon() {
+ if(unusableFileIcon == null)
+ unusableFileIcon = new UnusableFileIcon();
+ return unusableFileIcon;
+ }
+
+
+ static private final Dimension folderIcon16Size = new Dimension( 16, 16 );
+ static private final Dimension fileIcon16Size = new Dimension( 16, 16 );
+
+ /**
+ */
+ static class BlueFolderIcon extends FolderIcon16 implements Icon {
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ super.paintIcon(c, g, x, y,
+ MetalLookAndFeel.getPrimaryControlDarkShadow(),
+ MetalLookAndFeel.getPrimaryControl(),
+ MetalLookAndFeel.getPrimaryControlShadow(),
+ MetalLookAndFeel.getPrimaryControlInfo(),
+ MetalLookAndFeel.getPrimaryControlHighlight());
+ }
+ public int getShift() { return -1; }
+ public int getAdditionalHeight() { return 2; }
+ }
+
+ static class OrangeFolderIcon extends FolderIcon16 implements Icon {
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ super.paintIcon(c, g, x, y,
+ orangeDarkShadowColor,
+ orangeColor,
+ orangeShadowColor,
+ orangeInfoColor,
+ orangeHighlightColor);
+ }
+ public int getShift() { return -1; }
+ public int getAdditionalHeight() { return 2; }
+ }
+
+ static class GrayFolderIcon extends FolderIcon16 implements Icon {
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ super.paintIcon(c, g, x, y,
+ grayDarkShadowColor,
+ grayColor,
+ grayShadowColor,
+ grayInfoColor,
+ grayHighlightColor);
+ }
+ public int getShift() { return -1; }
+ public int getAdditionalHeight() { return 2; }
+ }
+
+ static class UnusableFolderIcon extends FolderIcon16 implements Icon {
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ super.paintIcon(c, g, x, y,
+ grayDarkShadowColor,
+ grayColor,
+ grayShadowColor,
+ grayInfoColor,
+ grayHighlightColor);
+ super.drawCross(c, g, x, y);
+ }
+ public int getShift() { return -1; }
+ public int getAdditionalHeight() { return 2; }
+ }
+
+ /**
+ */
+ private static abstract class FolderIcon16 implements Serializable {
+
+ public void paintIcon(Component c, Graphics g, int x, int y,
+ Color controlDarkShadowColor,
+ Color controlColor,
+ Color controlShadowColor,
+ Color controlInfoColor,
+ Color controlHighlightColor) {
+ g.translate( x, y + getShift() );
+
+ int right = folderIcon16Size.width - 1;
+ int bottom = folderIcon16Size.height - 1;
+
+ // Draw tab top
+ g.setColor( controlDarkShadowColor );
+ g.drawLine( right - 5, 3, right, 3 );
+ g.drawLine( right - 6, 4, right, 4 );
+
+ // Draw folder front
+ g.setColor( controlColor );
+ g.fillRect( 2, 7, 13, 8 );
+
+ // Draw tab bottom
+ g.setColor( controlShadowColor );
+ g.drawLine( right - 6, 5, right - 1, 5 );
+
+ // Draw outline
+ g.setColor( controlInfoColor );
+ g.drawLine( 0, 6, 0, bottom ); // left side
+ g.drawLine( 1, 5, right - 7, 5 ); // first part of top
+ g.drawLine( right - 6, 6, right - 1, 6 ); // second part of top
+ g.drawLine( right, 5, right, bottom ); // right side
+ g.drawLine( 0, bottom, right, bottom ); // bottom
+
+ // Draw highlight
+ g.setColor( controlHighlightColor );
+ g.drawLine( 1, 6, 1, bottom - 1 );
+ g.drawLine( 1, 6, right - 7, 6 );
+ g.drawLine( right - 6, 7, right - 1, 7 );
+
+ g.translate( -x, -(y + getShift()) );
+ }
+
+ public void drawCross(Component c, Graphics g, int x, int y) {
+ g.translate( x, y + getShift() );
+
+ int right = folderIcon16Size.width - 1;
+ int bottom = folderIcon16Size.height - 1;
+
+ // Draw tab top
+ Color crossColor = Color.red;
+ g.setColor( crossColor );
+ g.drawLine( 2, 2, right, bottom );
+ g.drawLine( 2, bottom, right, 2 );
+ }
+
+ public int getShift() { return 0; }
+ public int getAdditionalHeight() { return 0; }
+
+ public int getIconWidth() { return folderIcon16Size.width; }
+ public int getIconHeight() { return folderIcon16Size.height + getAdditionalHeight(); }
+ }
+
+
+
+
+ static class BlueFileIcon extends FileIcon16 implements Icon {
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ super.paintIcon(c, g, x, y,
+ MetalLookAndFeel.getPrimaryControlHighlight(),
+ MetalLookAndFeel.getPrimaryControlInfo(),
+ MetalLookAndFeel.getPrimaryControl());
+ }
+ public int getShift() { return -1; }
+ public int getAdditionalHeight() { return 2; }
+ }
+
+ static class OrangeFileIcon extends FileIcon16 implements Icon {
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ super.paintIcon(c, g, x, y,
+ orangeHighlightColor,
+ orangeInfoColor,
+ orangeColor);
+ }
+ public int getShift() { return -1; }
+ public int getAdditionalHeight() { return 2; }
+ }
+
+ static class GrayFileIcon extends FileIcon16 implements Icon {
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ super.paintIcon(c, g, x, y,
+ grayHighlightColor,
+ grayInfoColor,
+ grayColor);
+ }
+ public int getShift() { return -1; }
+ public int getAdditionalHeight() { return 2; }
+ }
+
+ static class UnusableFileIcon extends FileIcon16 implements Icon {
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ super.paintIcon(c, g, x, y,
+ grayHighlightColor,
+ grayInfoColor,
+ grayColor);
+ super.drawCross(c, g, x, y);
+ }
+ public int getShift() { return -1; }
+ public int getAdditionalHeight() { return 2; }
+ }
+
+ /**
+ */
+ private static abstract class FileIcon16 implements Serializable {
+ public void paintIcon(Component c, Graphics g, int x, int y,
+ Color controlHighlightColor,
+ Color controlInfoColor,
+ Color controlColor) {
+ g.translate( x, y + getShift() );
+
+ int right = fileIcon16Size.width - 1;
+ int bottom = fileIcon16Size.height - 1;
+
+ // Draw fill
+ g.setColor( controlHighlightColor );
+ g.fillRect( 4, 2, 9, 12 );
+
+ // Draw frame
+ g.setColor( controlInfoColor );
+ g.drawLine( 2, 0, 2, bottom ); // left
+ g.drawLine( 2, 0, right - 4, 0 ); // top
+ g.drawLine( 2, bottom, right - 1, bottom ); // bottom
+ g.drawLine( right - 1, 6, right - 1, bottom ); // right
+ g.drawLine( right - 6, 2, right - 2, 6 ); // slant 1
+ g.drawLine( right - 5, 1, right - 4, 1 ); // part of slant 2
+ g.drawLine( right - 3, 2, right - 3, 3 ); // part of slant 2
+ g.drawLine( right - 2, 4, right - 2, 5 ); // part of slant 2
+
+ // Draw highlight
+ g.setColor( controlColor );
+ g.drawLine( 3, 1, 3, bottom - 1 ); // left
+ g.drawLine( 3, 1, right - 6, 1 ); // top
+ g.drawLine( right - 2, 7, right - 2, bottom - 1 ); // right
+ g.drawLine( right - 5, 2, right - 3, 4 ); // slant
+ g.drawLine( 3, bottom - 1, right - 2, bottom - 1 ); // bottom
+
+ g.translate( -x, -(y + getShift()) );
+ }
+
+ public void drawCross(Component c, Graphics g, int x, int y) {
+ g.translate( x, y + getShift() );
+
+ int right = folderIcon16Size.width - 1;
+ int bottom = folderIcon16Size.height - 1;
+
+ // Draw tab top
+ Color crossColor = Color.red;
+ g.setColor( crossColor );
+ g.drawLine( 1, 1, right - 1, bottom - 1 );
+ g.drawLine( 1, bottom - 1, right - 1, 1 );
+ }
+
+ public int getShift() { return 0; }
+ public int getAdditionalHeight() { return 0; }
+
+ public int getIconWidth() { return fileIcon16Size.width; }
+ public int getIconHeight() { return fileIcon16Size.height + getAdditionalHeight(); }
+ }
+
+
+ static class TreeLeafIcon extends FileIcon16 {
+ public int getShift() { return 2; }
+ public int getAdditionalHeight() { return 4; }
+ }
+}
+
diff --git a/browser/src/main/java/org/apache/river/container/examples/browser/ObjectNode.java b/browser/src/main/java/org/apache/river/container/examples/browser/ObjectNode.java
new file mode 100644
index 0000000..6409ba1
--- /dev/null
+++ b/browser/src/main/java/org/apache/river/container/examples/browser/ObjectNode.java
@@ -0,0 +1,316 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.examples.browser;
+
+import java.lang.reflect.Field;
+import javax.swing.tree.TreeModel;
+import javax.swing.tree.TreePath;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.Icon;
+
+/**
+ *
+ * @author Sun Microsystems, Inc.
+ *
+ * @version 0.2 06/04/98
+ *
+ */
+class ObjectNode extends DefaultMutableTreeNode implements java.io.Serializable {
+
+ // "classname", "classname fieldName" or "classname fieldName=value"
+ /**
+ * @serial
+ */
+ private String name;
+
+ /**
+ * @serial
+ */
+ private Object obj;
+
+ /**
+ * @serial
+ */
+ private Class clazz;
+
+ /**
+ * @serial
+ */
+ private String fieldName;
+
+ /**
+ * @serial
+ */
+ private int arrayIndex = -1;
+
+ /**
+ * @serial
+ */
+ private boolean editable = false;
+
+ /**
+ * @serial
+ */
+ private boolean isLeaf;
+
+ /**
+ * @serial
+ */
+ private boolean isAdministrable; // root level
+
+ /**
+ * @serial
+ */
+ private boolean isControllable; // entry level
+
+ /**
+ * @serial
+ */
+ private boolean isRoot = false;
+
+ /**
+ * @serial
+ */
+ private boolean isEntryTop = false;
+
+ // icons
+ private static Icon[] icons = new Icon[6];
+ static {
+ icons[0] = MetalIcons.getBlueFolderIcon(); // Administrable Service, Controllable Attribute
+ icons[1] = MetalIcons.getGrayFolderIcon(); // Non-administrable Service
+ icons[2] = MetalIcons.getOrangeFolderIcon(); // Uncontrollable Attribute
+ icons[3] = MetalIcons.getBlueFileIcon(); // Administrable Service, Controllable Attribute
+ icons[4] = MetalIcons.getGrayFileIcon(); // Non-administrable Service
+ icons[5] = MetalIcons.getOrangeFileIcon(); // Uncontrollable Attribute
+ }
+
+ /**
+ Constructor for a root node.
+ */
+ public ObjectNode(boolean isAdministrable) {
+ this("Root node", "".getClass(), null, -1, false);
+
+ this.isAdministrable = isAdministrable;
+ this.isRoot = true;
+ this.isEntryTop = true;
+ }
+
+ /**
+ Constructor for an entry (attribute) top nodes.
+ */
+ public ObjectNode(Object obj, boolean isControllable) {
+ this(obj, obj.getClass(), null, -1, false);
+
+ this.isControllable = isControllable;
+ this.isEntryTop = true;
+ }
+
+ /**
+ Constructor for an ordinary field.
+ */
+ public ObjectNode(Object obj, Class clazz, String fieldName, boolean isLeaf) {
+ this(obj, clazz, fieldName, -1, isLeaf);
+ }
+
+ /**
+ Constructor for an array element.
+ */
+ public ObjectNode(Object obj, Class clazz, String fieldName, int arrayIndex, boolean isLeaf) {
+ this.obj = obj;
+ this.clazz = clazz;
+ this.fieldName = fieldName;
+ this.arrayIndex = arrayIndex;
+ this.isLeaf = isLeaf;
+
+ super.setAllowsChildren(! isLeaf);
+ setNodeName();
+ }
+
+ private void setNodeName() {
+ name = Introspector.getTypename(clazz, false);
+ if(fieldName != null)
+ name += " " + fieldName;
+
+ if(isLeaf) {
+ //Class clazz = obj.getClass();
+ String value = "";
+ if(arrayIndex >= 0)
+ value += ("[" + arrayIndex + "]");
+ value += "=";
+ if(clazz.isPrimitive()){
+ value += "" + obj;
+ editable = false;
+ } else if(Introspector.isWrapper(clazz)){
+ // Wrapper objects
+ value += "" + obj;
+ editable = true;
+ } else if(Introspector.isString(clazz)) {
+ value += "\"" + obj + "\"";
+ editable = true;
+ } else {
+ value += (obj == null ? "null" : obj.toString());
+ }
+ name += value;
+ } else if(obj == null) {
+ name += "=null";
+ }
+
+ super.setUserObject(name);
+ }
+
+ public void add(ObjectNode child){
+ child.setAdministrable(isAdministrable);
+ if(! isRoot){
+ child.setControllable(isControllable);
+ }
+
+ super.add(child);
+ }
+
+ public Object getEntryTop() {
+ ObjectNode snode = this;
+ do {
+ snode = (ObjectNode) snode.getParent();
+ } while(! snode.isEntryTop());
+
+ return snode.getObject();
+ }
+
+ protected boolean isEntryTop() {
+ return isEntryTop;
+ }
+
+ public void setObjectRecursive() throws NoSuchFieldException, IllegalAccessException {
+ ObjectNode pnode = this;
+ do {
+ pnode = (ObjectNode) pnode.getParent();
+ Object pobj = pnode.getObject();
+ // Needs to think about array modifications
+ Field f = pobj.getClass().getField(fieldName);
+ f.set(pobj, obj);
+ } while(! pnode.isEntryTop());
+ }
+
+ public String getTitle() {
+ return name;
+ }
+
+ public Icon getIcon() {
+ /*
+ if(isAdministrable){
+ if(isControllable){
+ if(isLeaf) return icons[3];
+ else return icons[0];
+ } else {
+ if(isLeaf) return icons[5];
+ else return icons[2];
+ }
+ } else {
+ if(isLeaf) return icons[4];
+ else return icons[1];
+ }
+ */
+ if(isAdministrable && isControllable)
+ if(isLeaf) return icons[3];
+ else return icons[0];
+ else
+ if(isLeaf) return icons[4];
+ else return icons[1];
+ }
+
+
+ // Overwrite
+ public void setUserObject(Object obj){
+ if(obj instanceof String)
+ name = (String) obj;
+
+ super.setUserObject(obj);
+ }
+
+ public Object getUserObject(){
+ return name;
+ }
+
+ public String toString() {
+ return name;
+ }
+
+ public String getFieldName() {
+ return fieldName;
+ }
+
+ public Object getObject() {
+ return obj;
+ }
+
+ public boolean isEditable() {
+ return editable;
+ }
+
+ public boolean isAdministrable() {
+ return isAdministrable;
+ }
+
+ public void setAdministrable(boolean val) {
+ isAdministrable = val;
+ }
+
+ public boolean isControllable() {
+ return isControllable;
+ }
+
+ public void setControllable(boolean val) {
+ isControllable = val;
+ }
+
+ public Object setValue(Object val) throws NumberFormatException {
+ String clazzName = clazz.getName();
+ Object newObj = null;
+
+ if(val instanceof String || val == null) {
+ String sval = (String) val;
+ if(clazzName.equals("java.lang.Integer"))
+ newObj = new Integer(sval);
+ else if(clazzName.equals("java.lang.Boolean"))
+ newObj = new Boolean(sval);
+ else if(clazzName.equals("java.lang.Byte"))
+ newObj = new Byte(sval);
+ else if(clazzName.equals("java.lang.Character"))
+ newObj = new Character(sval.charAt(0));
+ else if(clazzName.equals("java.lang.Double"))
+ newObj = new Double(sval);
+ else if(clazzName.equals("java.lang.Float"))
+ newObj = new Float(sval);
+ else if(clazzName.equals("java.lang.Long"))
+ newObj = new Long(sval);
+ else if(clazzName.equals("java.lang.String"))
+ newObj = new String(sval); // clone
+ } else if(val.getClass().equals(obj.getClass())) {
+ // same class type
+ newObj = val;
+ }
+
+ Object oldObj = obj;
+ obj = newObj;
+
+ setNodeName();
+
+ return oldObj;
+ }
+}
diff --git a/browser/src/main/java/org/apache/river/container/examples/browser/ServiceBrowser.java b/browser/src/main/java/org/apache/river/container/examples/browser/ServiceBrowser.java
new file mode 100644
index 0000000..6af743b
--- /dev/null
+++ b/browser/src/main/java/org/apache/river/container/examples/browser/ServiceBrowser.java
@@ -0,0 +1,282 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.examples.browser;
+
+import net.jini.core.lookup.ServiceItem;
+import net.jini.core.lookup.ServiceRegistrar;
+import net.jini.core.lookup.ServiceMatches;
+import net.jini.core.lookup.ServiceTemplate;
+import net.jini.core.entry.Entry;
+import net.jini.lookup.ui.factory.JFrameFactory;
+import net.jini.lookup.entry.UIDescriptor;
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.Rectangle;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.logging.Level;
+import javax.swing.JFrame;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JPopupMenu;
+import javax.swing.event.PopupMenuListener;
+import javax.swing.event.PopupMenuEvent;
+
+/**
+ * A browser utility to browse entries in a specified space.
+ *
+ * @author Sun Microsystems, Inc.
+ *
+ * @version 0.2 06/04/98
+ *
+ */
+class ServiceBrowser extends JFrame {
+ private Browser browser;
+ private AttributePanel attrPanel;
+ private final static int MINIMUM_WINDOW_WIDTH = 320;
+
+ public ServiceBrowser(ServiceItem item,
+ ServiceRegistrar registrar,
+ Browser browser)
+ {
+ super("ServiceItem Browser");
+
+ this.browser = browser;
+ // init main components
+ attrPanel = new AttributePanel(item, registrar);
+
+ // add menu and attr panel
+ getContentPane().setLayout(new BorderLayout());
+ getContentPane().add(new BrowserMenuBar(), "North");
+ getContentPane().add(attrPanel, "Center");
+
+ validate();
+ pack();
+ setSize(((getSize().width < MINIMUM_WINDOW_WIDTH) ? MINIMUM_WINDOW_WIDTH : getSize().width),
+ getSize().height);
+
+ // center in parent frame
+ Rectangle bounds = browser.getBounds();
+ Dimension dialogSize = getPreferredSize();
+ int xpos = bounds.x + (bounds.width - dialogSize.width)/ 2;
+ int ypos = bounds.y + (bounds.height - dialogSize.height)/2;
+ setLocation((xpos < 0) ? 0 : xpos,
+ (ypos < 0) ? 0 : ypos);
+ }
+
+
+ class BrowserMenuBar extends JMenuBar {
+ public BrowserMenuBar() {
+ JMenuItem mitem;
+
+ // "File" Menu
+ JMenu fileMenu = (JMenu) add(new JMenu("File"));
+ mitem = (JMenuItem) fileMenu.add(new JMenuItem("Refresh"));
+ mitem.addActionListener(browser.wrap(new ActionListener() {
+ public void actionPerformed(ActionEvent ev) {
+ attrPanel.refreshPanel();
+ }
+ }));
+ mitem = (JMenuItem) fileMenu.add(new JMenuItem("Close"));
+ mitem.addActionListener(browser.wrap(new ActionListener() {
+ public void actionPerformed(ActionEvent ev) {
+ ServiceBrowser.this.setVisible(false);
+ }
+ }));
+ }
+ }
+
+ class AttributePanel extends EntryTreePanel {
+ private ServiceItem item;
+ private ServiceRegistrar registrar;
+
+ public AttributePanel(ServiceItem item, ServiceRegistrar registrar) {
+ super(false); // Entries are not editable.
+
+ this.item = item;
+ this.registrar = registrar;
+
+ tree.addMouseListener(browser.wrap(new ServiceBrowser.MouseReceiver(item,
+ uiDescriptorPopup())));
+
+ refreshPanel();
+ }
+
+ protected Entry[] getEntryArray() {
+ try{
+ ServiceMatches matches = registrar.lookup(new ServiceTemplate(item.serviceID,
+ new Class[] { item.service.getClass() },
+ new Entry[] {}),
+ 10);
+ if(matches.totalMatches != 1)
+ Browser.logger.log(Level.INFO, "unexpected lookup matches: {0}",
+ new Integer(matches.totalMatches));
+ else
+ return matches.items[0].attributeSets;
+ } catch (Throwable t) {
+ Browser.logger.log(Level.INFO, "lookup failed", t);
+ }
+ return null;
+ }
+ }
+
+ // provides support for ServiceUI
+ public class UIDescriptorPopup extends JPopupMenu implements ActionListener,
+ PopupMenuListener {
+
+ protected transient JMenuItem showUIItem;
+ protected transient ServiceItem serviceItem;
+
+ public UIDescriptorPopup() {
+ super();
+
+ showUIItem = new JMenuItem("Show UI");
+
+ showUIItem.addActionListener(this);
+ showUIItem.setActionCommand("showUI");
+ add(showUIItem);
+
+ addPopupMenuListener(this);
+ setOpaque(true);
+ setLightWeightPopupEnabled(true);
+ }
+
+ public void actionPerformed(ActionEvent anEvent) {
+
+ UIDescriptor uiDescriptor = getSelectedUIDescriptor();
+
+ if (uiDescriptor == null) {
+ return;
+ }
+
+ try {
+ JFrameFactory uiFactory = (JFrameFactory)
+ uiDescriptor.getUIFactory(Thread.currentThread().getContextClassLoader());
+ JFrame frame = uiFactory.getJFrame(serviceItem);
+
+ frame.validate();
+ frame.setVisible(true);
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+
+ return;
+ }
+ }
+
+ public void popupMenuWillBecomeVisible(PopupMenuEvent ev) {
+ }
+
+ public void popupMenuWillBecomeInvisible(PopupMenuEvent ev) {
+ }
+
+ public void popupMenuCanceled(PopupMenuEvent ev) {
+ }
+
+ public void setServiceItem(ServiceItem anItem) {
+ serviceItem = anItem;
+ }
+ }
+
+ class MouseReceiver extends MouseAdapter {
+
+ private ServiceBrowser.UIDescriptorPopup popup;
+ private ServiceItem serviceItem;
+
+ public MouseReceiver(ServiceItem aServiceItem,
+ ServiceBrowser.UIDescriptorPopup popup) {
+ this.popup = popup;
+ serviceItem = aServiceItem;
+ }
+
+ public void mouseReleased(MouseEvent ev) {
+
+ higlightSelection(ev);
+
+ if (!ev.isPopupTrigger()) {
+ return;
+ }
+
+ UIDescriptor selectedDescriptor = getSelectedUIDescriptor();
+
+ if (selectedDescriptor == null) {
+ return;
+ }
+
+ if (!"javax.swing".equals(selectedDescriptor.toolkit)) {
+ return;
+ }
+
+ popup.setServiceItem(serviceItem);
+ popup.show(ev.getComponent(), ev.getX(), ev.getY());
+ }
+
+ public void mousePressed(MouseEvent ev) {
+
+ higlightSelection(ev);
+
+ if (!ev.isPopupTrigger()) {
+ return;
+ }
+
+ UIDescriptor selectedDescriptor = getSelectedUIDescriptor();
+
+ if (selectedDescriptor == null) {
+ return;
+ }
+
+ if (!"javax.swing".equals(selectedDescriptor.toolkit)) {
+ return;
+ }
+
+ popup.setServiceItem(serviceItem);
+ popup.show(ev.getComponent(), ev.getX(), ev.getY());
+ }
+ }
+
+ private UIDescriptor getSelectedUIDescriptor() {
+
+ ObjectNode selectedNode =
+ (ObjectNode) attrPanel.tree.getLastSelectedPathComponent();
+
+ if (selectedNode == null) {
+ return null;
+ }
+
+ Object selectedObject = selectedNode.getObject();
+
+ try {
+ return (UIDescriptor) selectedObject;
+ }
+ catch (ClassCastException e) {
+ return null;
+ }
+ }
+
+ private void higlightSelection(MouseEvent anEvent) {
+ attrPanel.tree.setSelectionPath(attrPanel.tree.getPathForLocation(
+ anEvent.getX(), anEvent.getY()));
+ }
+
+ private ServiceBrowser.UIDescriptorPopup uiDescriptorPopup() {
+ return new ServiceBrowser.UIDescriptorPopup();
+ }
+}
diff --git a/browser/src/main/java/org/apache/river/container/examples/browser/ServiceEditor.java b/browser/src/main/java/org/apache/river/container/examples/browser/ServiceEditor.java
new file mode 100644
index 0000000..ee972da
--- /dev/null
+++ b/browser/src/main/java/org/apache/river/container/examples/browser/ServiceEditor.java
@@ -0,0 +1,1213 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.examples.browser;
+
+import com.sun.jini.admin.DestroyAdmin;
+import com.sun.jini.config.Config;
+import com.sun.jini.logging.Levels;
+import com.sun.jini.proxy.BasicProxyTrustVerifier;
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.net.MalformedURLException;
+import java.rmi.server.ExportException;
+import java.util.StringTokenizer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.DefaultListModel;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JList;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.JTree;
+import javax.swing.SwingUtilities;
+import javax.swing.event.PopupMenuEvent;
+import javax.swing.event.PopupMenuListener;
+import javax.swing.tree.TreePath;
+import net.jini.admin.JoinAdmin;
+import net.jini.config.ConfigurationException;
+import net.jini.core.discovery.LookupLocator;
+import net.jini.core.entry.Entry;
+import net.jini.core.event.EventRegistration;
+import net.jini.core.event.RemoteEvent;
+import net.jini.core.event.RemoteEventListener;
+import net.jini.core.lease.Lease;
+import net.jini.core.lookup.ServiceEvent;
+import net.jini.core.lookup.ServiceItem;
+import net.jini.core.lookup.ServiceMatches;
+import net.jini.core.lookup.ServiceRegistrar;
+import net.jini.core.lookup.ServiceTemplate;
+import net.jini.export.Exporter;
+import net.jini.jeri.BasicILFactory;
+import net.jini.jeri.BasicJeriExporter;
+import net.jini.jeri.tcp.TcpServerEndpoint;
+import net.jini.lookup.DiscoveryAdmin;
+import net.jini.lookup.entry.UIDescriptor;
+import net.jini.lookup.ui.factory.JFrameFactory;
+import net.jini.security.TrustVerifier;
+import net.jini.security.proxytrust.ServerProxyTrust;
+
+/**
+ * ServiceEditor is a gui-based utility to add/modify
+ * attributes, groups and lookup locators.
+ * And also supports some well known admin interfaces.
+ * (DestroyAdmin, DiscoveryAdmin)
+ *
+ * <p>
+ * current issues<br>
+ * <ul>
+ * <li> can't operate(add/remove/modify) array elements in an entry.
+ * <li> does not support EntryBean.
+ * <li> field modification is not based on EditableTree
+ * </ul>
+ *
+ * @author Sun Microsystems, Inc.
+ */
+class ServiceEditor extends JFrame {
+ private static final Logger logger = Browser.logger;
+
+ private Browser browser;
+ private ServiceItem item;
+ private ServiceRegistrar registrar;
+ protected Object admin;
+ private ServiceTemplate stmpl;
+ private NotifyReceiver receiver;
+ private Lease elease = null;
+ private long eventID = 0;
+ private long seqNo = Long.MAX_VALUE;
+ private AttributeTreePanel attrPanel;
+
+ private final static int MINIMUM_WINDOW_WIDTH = 320;
+
+ public ServiceEditor(ServiceItem item,
+ Object admin,
+ ServiceRegistrar registrar,
+ Browser browser)
+ {
+ super("ServiceItem Editor");
+
+ this.item = item;
+ this.admin = admin;
+ this.registrar = registrar;
+ this.browser = browser;
+
+ // init main components
+ attrPanel = new AttributeTreePanel();
+
+ // setup notify
+ try {
+ stmpl = new ServiceTemplate(item.serviceID,
+ new Class[] { item.service.getClass() },
+ new Entry[] {});
+ receiver = new NotifyReceiver();
+
+ setupNotify();
+ } catch (Throwable t) {
+ logger.log(Level.INFO, "event registration failed", t);
+ cancelNotify();
+ }
+
+ addWindowListener(browser.wrap(new WindowAdapter() {
+ public void windowClosing(WindowEvent e) {
+ cleanup();
+ }
+ }));
+ // add menu and attr panel
+ getContentPane().setLayout(new BorderLayout());
+ getContentPane().add(new JoinMenuBar(), "North");
+ getContentPane().add(attrPanel, "Center");
+
+ validate();
+ pack();
+ setSize(((getSize().width < MINIMUM_WINDOW_WIDTH) ? MINIMUM_WINDOW_WIDTH : getSize().width),
+ getSize().height);
+
+ // center in parent frame
+ Rectangle bounds = browser.getBounds();
+ Dimension dialogSize = getPreferredSize();
+ int xpos = bounds.x + (bounds.width - dialogSize.width)/ 2;
+ int ypos = bounds.y + (bounds.height - dialogSize.height)/2;
+ setLocation((xpos < 0) ? 0 : xpos,
+ (ypos < 0) ? 0 : ypos);
+ }
+
+ void cleanup() {
+ // cancel lease
+ cancelNotify();
+ // release resources and close all child frames
+ dispose();
+ receiver.unexport();
+ }
+
+ protected void cancelNotify() {
+ if(elease != null) {
+ try {
+ browser.leaseMgr.cancel(elease);
+ } catch (Throwable t) {
+ logger.log(Levels.HANDLED, "event cancellation failed", t);
+ }
+ elease = null;
+ seqNo = Long.MAX_VALUE;
+ }
+ }
+
+ protected void setupNotify() {
+ if(registrar != null) {
+ try {
+ EventRegistration reg =
+ registrar.notify(stmpl,
+ ServiceRegistrar.TRANSITION_MATCH_NOMATCH |
+ ServiceRegistrar.TRANSITION_NOMATCH_MATCH |
+ ServiceRegistrar.TRANSITION_MATCH_MATCH,
+ receiver.proxy,
+ null,
+ Lease.ANY);
+ elease = (Lease) browser.leasePreparer.prepareProxy(reg.getLease());
+ browser.leaseMgr.renewUntil(elease, Lease.ANY,
+ new Browser.LeaseNotify());
+ eventID = reg.getID();
+ seqNo = reg.getSequenceNumber();
+ } catch (Throwable t) {
+ logger.log(Level.INFO, "event registration failed", t);
+ }
+ }
+ }
+
+ private class NotifyReceiver implements RemoteEventListener, ServerProxyTrust
+ {
+ private final Exporter exporter;
+ final RemoteEventListener proxy;
+
+ public NotifyReceiver() throws ConfigurationException, ExportException {
+ exporter = (Exporter)
+ Config.getNonNullEntry(browser.config, Browser.BROWSER,
+ "listenerExporter", Exporter.class,
+ new BasicJeriExporter(
+ TcpServerEndpoint.getInstance(0),
+ new BasicILFactory(),
+ false, false));
+ proxy = (RemoteEventListener) exporter.export(this);
+ }
+
+ public void notify(final RemoteEvent ev) {
+ SwingUtilities.invokeLater(browser.wrap(new Runnable() {
+ public void run() {
+ if (eventID == ev.getID() && seqNo < ev.getSequenceNumber()) {
+ seqNo = ev.getSequenceNumber();
+ attrPanel.receiveNotify(((ServiceEvent) ev).getTransition());
+ }
+ }
+ }));
+ }
+
+ public TrustVerifier getProxyVerifier() {
+ return new BasicProxyTrustVerifier(proxy);
+ }
+
+ void unexport() {
+ exporter.unexport(true);
+ }
+ }
+
+ class JoinMenuBar extends JMenuBar {
+ public JoinMenuBar() {
+ JMenuItem mitem;
+
+ // "File" Menu
+ JMenu fileMenu = (JMenu) add(new JMenu("File"));
+ mitem = (JMenuItem) fileMenu.add(new JMenuItem("Show Info"));
+ mitem.addActionListener(browser.wrap(new ActionListener() {
+ public void actionPerformed(ActionEvent ev) {
+ Class[] infs = Browser.getInterfaces(item.service.getClass());
+ String[] msg = new String[3 + infs.length];
+ msg[0] = "ServiceID: " + item.serviceID;
+ msg[1] = "Service Instance: " + item.service.getClass().getName();
+ if(infs.length == 1)
+ msg[2] = "Implemented Interface:";
+ else
+ msg[2] = "Implemented Interfaces:";
+ for(int i = 0; i < infs.length; i++)
+ msg[3 + i] = " " + infs[i].getName();
+
+ JOptionPane.showMessageDialog(ServiceEditor.this,
+ msg,
+ "ServiceItem Information",
+ JOptionPane.INFORMATION_MESSAGE);
+ }
+ }));
+ mitem = (JMenuItem) fileMenu.add(new JMenuItem("Refresh"));
+ mitem.addActionListener(browser.wrap(new ActionListener() {
+ public void actionPerformed(ActionEvent ev) {
+ attrPanel.refreshPanel();
+ }
+ }));
+ mitem = (JMenuItem) fileMenu.add(new JMenuItem("Close"));
+ mitem.addActionListener(browser.wrap(new ActionListener() {
+ public void actionPerformed(ActionEvent ev) {
+ cleanup();
+ }
+ }));
+
+ // "Edit" Menu
+ JMenu editMenu = (JMenu) add(new JMenu("Edit"));
+ mitem = (JMenuItem) editMenu.add(new JMenuItem("Add Attribute..."));
+ mitem.addActionListener(browser.wrap(new ActionListener() {
+ public void actionPerformed(ActionEvent ev) {
+ attrPanel.addAttr();
+ }
+ }));
+ if(! (admin instanceof JoinAdmin))
+ mitem.setEnabled(false);
+ mitem = (JMenuItem) editMenu.add(new JMenuItem("Remove Attribute"));
+ mitem.addActionListener(browser.wrap(new ActionListener() {
+ public void actionPerformed(ActionEvent ev) {
+ attrPanel.removeAttr();
+ }
+ }));
+ if(! (admin instanceof JoinAdmin))
+ mitem.setEnabled(false);
+
+ // "Admin" Menu
+ JMenu adminMenu = (JMenu) add(new JMenu("Admin"));
+
+ // Group (JoinAdmin)
+ mitem = (JMenuItem) adminMenu.add(new JMenuItem("Joining groups..."));
+ mitem.addActionListener(browser.wrap(new ActionListener() {
+ public void actionPerformed(ActionEvent ev) {
+ new GroupLister("Joining Groups").showFrame();
+ }
+ }));
+ if(! (admin instanceof JoinAdmin))
+ mitem.setEnabled(false);
+
+ // Locator (JoinAdmin)
+ mitem = (JMenuItem) adminMenu.add(new JMenuItem("Joining locators..."));
+ mitem.addActionListener(browser.wrap(new ActionListener() {
+ public void actionPerformed(ActionEvent ev) {
+ new LocatorLister("Joining Locators").showFrame();
+ }
+ }));
+ if(! (admin instanceof JoinAdmin))
+ mitem.setEnabled(false);
+
+ // separator
+ adminMenu.addSeparator();
+
+ // Group (DiscoveryAdmin)
+ mitem = (JMenuItem) adminMenu.add(new JMenuItem("Member groups..."));
+ mitem.addActionListener(browser.wrap(new ActionListener() {
+ public void actionPerformed(ActionEvent ev) {
+ new MemberGroupLister("Member Groups").showFrame();
+ }
+ }));
+ if(! (admin instanceof DiscoveryAdmin))
+ mitem.setEnabled(false);
+
+ // Unicast port (DiscoveryAdmin)
+ mitem = (JMenuItem) adminMenu.add(new JMenuItem("Unicast port..."));
+ mitem.addActionListener(browser.wrap(new ActionListener() {
+ public void actionPerformed(ActionEvent ev) {
+ try {
+ String[] msg = { "Current port is " + ((DiscoveryAdmin) admin).getUnicastPort(),
+ "Input a new value" };
+ String result = JOptionPane.showInputDialog(ServiceEditor.this,
+ msg,
+ "Unicast Port",
+ JOptionPane.QUESTION_MESSAGE);
+
+ if(result == null)
+ return;
+
+ try {
+ int port = Integer.parseInt(result);
+ ((DiscoveryAdmin) admin).setUnicastPort(port);
+ } catch (NumberFormatException e) {
+ JOptionPane.showMessageDialog(ServiceEditor.this,
+ result + " is not acceptable.",
+ "Error",
+ JOptionPane.ERROR_MESSAGE);
+ } catch (Throwable t) {
+ logger.log(Level.INFO, "setting unicast port failed", t);
+ JOptionPane.showMessageDialog(ServiceEditor.this,
+ t.getMessage(),
+ t.getClass().getName(),
+ JOptionPane.ERROR_MESSAGE);
+ }
+ } catch (Throwable t) {
+ logger.log(Level.INFO, "getting unicast port failed", t);
+ JOptionPane.showMessageDialog(ServiceEditor.this,
+ t.getMessage(),
+ t.getClass().getName(),
+ JOptionPane.ERROR_MESSAGE);
+ }
+ }
+ }));
+ if(! (admin instanceof DiscoveryAdmin))
+ mitem.setEnabled(false);
+
+ // separator
+ adminMenu.addSeparator();
+
+ // DestroyAdmin
+ mitem = (JMenuItem) adminMenu.add(new JMenuItem("Destroy"));
+ mitem.addActionListener(browser.wrap(new ActionListener() {
+ public void actionPerformed(ActionEvent ev) {
+ if(JOptionPane.showConfirmDialog(ServiceEditor.this,
+ "Are you sure to destroy this service?",
+ "Query",
+ JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION)
+ try {
+ ((DestroyAdmin) admin).destroy();
+ cleanup();
+ } catch (Throwable t) {
+ logger.log(Level.INFO, "service destroy failed", t);
+ JOptionPane.showMessageDialog(ServiceEditor.this,
+ t.getMessage(),
+ t.getClass().getName(),
+ JOptionPane.ERROR_MESSAGE);
+ }
+ }
+ }));
+ if(! (admin instanceof DestroyAdmin))
+ mitem.setEnabled(false);
+ }
+ }
+
+ class AttributeTreePanel extends EntryTreePanel {
+
+ public AttributeTreePanel() {
+ super(admin instanceof JoinAdmin);
+
+ if(admin instanceof JoinAdmin) {
+ tree.addMouseListener(browser.wrap(new DoubleClicker(this)));
+ }
+ tree.addMouseListener(browser.wrap(new MouseReceiver(item, uiDescriptorPopup())));
+
+ refreshPanel();
+ }
+
+ protected Entry[] getEntryArray() {
+ if(admin instanceof JoinAdmin) {
+ try {
+ item.attributeSets = ((JoinAdmin) admin).getLookupAttributes();
+ } catch (Throwable t) {
+ logger.log(Level.INFO, "obtaining attributes failed", t);
+ }
+ } else {
+ try{
+ ServiceMatches matches = registrar.lookup(stmpl, 1);
+ if(matches.totalMatches != 1)
+ Browser.logger.log(Level.INFO, "unexpected lookup matches: {0}",
+ new Integer(matches.totalMatches));
+ else
+ item.attributeSets = matches.items[0].attributeSets;
+ } catch (Throwable t) {
+ Browser.logger.log(Level.INFO, "lookup failed", t);
+ }
+ }
+ return item.attributeSets;
+ }
+
+ protected void receiveNotify(int transition) {
+
+ if (browser.isAutoConfirm()) {
+ if (transition == ServiceRegistrar.TRANSITION_MATCH_NOMATCH)
+ cleanup();
+ else
+ refreshPanel();
+
+ return;
+ }
+
+ String[] msg =
+ (transition == ServiceRegistrar.TRANSITION_MATCH_NOMATCH) ?
+ new String[]{
+ "Service has been removed from lookup service.",
+ "Do you want to close the service editor window ?"} :
+ new String[]{
+ "Attributes have been modified by another client or the service itself.",
+ "Do you want to refresh the attributes ?"};
+ int result = JOptionPane.showConfirmDialog(AttributeTreePanel.this,
+ msg,
+ "Query",
+ JOptionPane.YES_NO_OPTION);
+
+ if(result == JOptionPane.YES_OPTION) {
+ if (transition == ServiceRegistrar.TRANSITION_MATCH_NOMATCH)
+ cleanup();
+ else
+ refreshPanel();
+ }
+ }
+
+ public void editField(ObjectNode node) {
+
+ String result = JOptionPane.showInputDialog(this,
+ "Input a new value",
+ "Modify a field",
+ JOptionPane.QUESTION_MESSAGE);
+
+ if(result == null){
+ } else {
+ // Save current value as template
+ Entry template = cloneEntry((Entry) node.getEntryTop());
+ Object oldVal = null;
+
+ if(result.length() == 0){
+ oldVal = node.setValue(null);
+ } else {
+ oldVal = node.setValue(result);
+ }
+ // modifyAttribute
+ try {
+ node.setObjectRecursive();
+ Entry attr = (Entry) node.getEntryTop();
+ //Entry template = (Entry) generateTemplate(attr);
+
+ // cancel notify while adding an attribute
+ cancelNotify();
+
+ ((JoinAdmin) admin).modifyLookupAttributes(
+ new Entry[] { template }, new Entry[] { attr });
+
+ setupNotify();
+
+ // Redraw node value
+ model.nodeChanged(node);
+ } catch (Throwable t) {
+ logger.log(Level.INFO, "attribute modification failed", t);
+ // recover tree node
+ try {
+ node.setValue(oldVal);
+ node.setObjectRecursive();
+ } catch (Throwable tt) {
+ logger.log(Levels.HANDLED, "node reset failed", tt);
+ }
+ model.nodeChanged(node);
+ //model.nodeStructureChanged(node);
+
+ // show dialog
+ JOptionPane.showMessageDialog(AttributeTreePanel.this,
+ t.getMessage(),
+ t.getClass().getName(),
+ JOptionPane.ERROR_MESSAGE);
+ }
+ }
+ }
+
+ public void addAttr() {
+
+ String result = JOptionPane.showInputDialog(this,
+ "Input an entry class name",
+ "Add an attribute",
+ JOptionPane.QUESTION_MESSAGE);
+
+ if(result == null || result.length() == 0){
+ } else {
+ try {
+ Class clazz = Class.forName(result);
+ Object attr = clazz.newInstance();
+
+ if(! (attr instanceof Entry)){
+ JOptionPane.showMessageDialog(AttributeTreePanel.this,
+ "Does not implement Entry interface",
+ "Unacceptable Class",
+ JOptionPane.WARNING_MESSAGE);
+
+ } else if(attr instanceof net.jini.lookup.entry.ServiceControlled){
+ JOptionPane.showMessageDialog(AttributeTreePanel.this,
+ "Implements ServiceControlled interface",
+ "Unacceptable Class",
+ JOptionPane.WARNING_MESSAGE);
+ } else {
+ // cancel notify while adding an attribute
+ cancelNotify();
+
+ ((JoinAdmin) admin).addLookupAttributes(
+ new Entry[] { (Entry) attr });
+ // add node of this attribute
+ ObjectNode node = new ObjectNode(attr, true);
+ root.add(node);
+ recursiveObjectTree(node);
+
+ //
+ setupNotify();
+
+ // refresh view
+ model.nodesWereInserted(root,
+ new int[] { model.getIndexOfChild(root, node) });
+ }
+ } catch (ClassNotFoundException e) {
+ JOptionPane.showMessageDialog(AttributeTreePanel.this,
+ e.getMessage(),
+ "Class Not Found",
+ JOptionPane.WARNING_MESSAGE);
+ } catch (Throwable t) {
+ logger.log(Level.INFO, "adding attribute failed", t);
+ JOptionPane.showMessageDialog(AttributeTreePanel.this,
+ t.getMessage(),
+ t.getClass().getName(),
+ JOptionPane.WARNING_MESSAGE);
+ }
+ }
+ }
+
+ public void removeAttr() {
+
+ ObjectNode node = (ObjectNode) tree.getLastSelectedPathComponent();
+ if(node == null){
+ JOptionPane.showMessageDialog(AttributeTreePanel.this,
+ "Select an attribute folder to remove.",
+ "Warning",
+ JOptionPane.WARNING_MESSAGE);
+
+ return;
+ } else if(! node.isControllable()){
+ JOptionPane.showMessageDialog(AttributeTreePanel.this,
+ "This attribute is under service provider's control.",
+ "Warning",
+ JOptionPane.WARNING_MESSAGE);
+
+ return;
+ } else if(! node.isEntryTop()){
+ JOptionPane.showMessageDialog(AttributeTreePanel.this,
+ "Select a top of attribute folder.",
+ "Warning",
+ JOptionPane.WARNING_MESSAGE);
+
+ return;
+ }
+
+ Entry target = (Entry) node.getObject();
+ int result = JOptionPane.showConfirmDialog(AttributeTreePanel.this,
+ new String[] {"Remove attribute:",
+ target.toString() },
+ "Query",
+ JOptionPane.YES_NO_OPTION);
+
+ if(result == JOptionPane.YES_OPTION){
+ // Remote Attribute
+ try {
+
+ // cancel notify while adding an attribute
+ cancelNotify();
+
+ ((JoinAdmin) admin).modifyLookupAttributes(
+ new Entry[] { target }, new Entry[] { null });
+
+ //
+ setupNotify();
+
+ int index = root.getIndex(node);
+ root.remove(node);
+ model.nodesWereRemoved(root, new int[] {index}, new Object[] {node});
+ node = null;
+ } catch (Throwable t) {
+ logger.log(Level.INFO, "attribute removal failed", t);
+ JOptionPane.showMessageDialog(AttributeTreePanel.this,
+ t.getMessage(),
+ t.getClass().getName(),
+ JOptionPane.ERROR_MESSAGE);
+ }
+ }
+ }
+
+ private Entry cloneEntry(Entry attr) {
+ try {
+ Class realClass = attr.getClass();
+ Entry template = (Entry) realClass.newInstance();
+
+ Field[] f = realClass.getFields();
+ for(int i = 0; i < f.length; i++) {
+ if(! usableField(f[i]))
+ continue;
+ f[i].set(template, f[i].get(attr));
+ }
+
+ return template;
+ } catch (Throwable t) {
+ logger.log(Level.INFO, "duplicating entry failed", t);
+ }
+ return null;
+ }
+
+ // from EntryRep
+ private boolean usableField(Field field) {
+ Class desc = field.getDeclaringClass();
+
+ if(desc.isPrimitive()) {
+ throw new IllegalArgumentException("primitive types not allowed in an Entry");
+ }
+
+ // skip anything that isn't a public per-object mutable field
+ int mods = field.getModifiers();
+ return (0 == (mods & (Modifier.TRANSIENT | Modifier.STATIC | Modifier.FINAL)));
+ }
+
+ private Entry generateTemplate(Entry attr) {
+ try {
+ Class realClass = attr.getClass();
+ Entry template = (Entry) realClass.newInstance();
+
+ Field[] f = realClass.getFields();
+ for(int i = 0; i < f.length; i++)
+ f[i].set(template, null);
+
+ return template;
+ } catch (Throwable t) {
+ logger.log(Level.INFO, "instantiating template failed", t);
+ }
+ return null;
+ }
+
+ class DoubleClicker extends MouseAdapter {
+ AttributeTreePanel parent;
+
+ public DoubleClicker(AttributeTreePanel parent){
+ this.parent = parent;
+ }
+
+ public void mouseClicked(MouseEvent ev){
+ if(ev.getClickCount() >= 2){
+ JTree tree = (JTree) ev.getSource();
+ TreePath path = tree.getPathForLocation(ev.getX(), ev.getY());
+ if(path == null)
+ return;
+ ObjectNode node = (ObjectNode) path.getLastPathComponent();
+
+ if(node.isLeaf()){
+ if(! node.isControllable()){
+ JOptionPane.showMessageDialog(AttributeTreePanel.this,
+ "This attribute is under service provider's control.",
+ "Warning",
+ JOptionPane.WARNING_MESSAGE);
+ } else if(node.isEditable() &&
+ ((ObjectNode) node.getParent()).isEntryTop())
+ {
+ parent.editField(node);
+ } else {
+ JOptionPane.showMessageDialog(AttributeTreePanel.this,
+ "This field is not editable.",
+ "Warning",
+ JOptionPane.WARNING_MESSAGE);
+ }
+ }
+
+ tree.scrollPathToVisible(path);
+ }
+ }
+ }
+ }
+
+ abstract class ListerFrame extends JFrame {
+
+ private JList listBox;
+ private JScrollPane scrollPane;
+ protected DefaultListModel model = new DefaultListModel();
+ private DefaultListModel dummyModel = new DefaultListModel(); // to keep away from Swing's bug
+
+ private JButton addButton;
+ private JButton removeButton;
+ private JButton closeButton;
+
+ public ListerFrame(String title) {
+ super(title);
+
+ getContentPane().setLayout(new BorderLayout());
+
+ // create the initial list
+ listBox = new JList(model);
+ listBox.setFixedCellHeight(20);
+ scrollPane = new JScrollPane(listBox);
+ getContentPane().add(scrollPane, "Center");
+ //resetListModel();
+
+ // Create the controls
+ JPanel buttonPanel = new JPanel();
+ addButton = new JButton("Add");
+ addButton.addActionListener(browser.wrap(new ActionListener() {
+ public void actionPerformed(ActionEvent ev) {
+ String result = JOptionPane.showInputDialog(ListerFrame.this, getAddMessage());
+
+ if(result != null){
+ StringTokenizer st = new StringTokenizer(result);
+ String[] tokens = new String[st.countTokens()];
+ for(int i = 0; i < tokens.length; i++)
+ tokens[i] = st.nextToken().trim();
+
+ addItems(tokens);
+ resetListModel();
+ scrollPane.validate();
+ }
+ }
+ }));
+ buttonPanel.add(addButton);
+
+ removeButton = new JButton("Remove");
+ removeButton.addActionListener(browser.wrap(new ActionListener() {
+ public void actionPerformed(ActionEvent ev) {
+ Object[] selected = listBox.getSelectedValues();
+
+ if(selected == null || selected.length == 0){
+ // no items are selected
+ JOptionPane.showMessageDialog(ListerFrame.this,
+ "No items are selected",
+ "Warning",
+ JOptionPane.WARNING_MESSAGE);
+ return;
+ }
+
+ int result = JOptionPane.showConfirmDialog(ListerFrame.this,
+ getRemoveMessage(selected),
+ "Query",
+ JOptionPane.YES_NO_OPTION);
+
+ if(result == JOptionPane.YES_OPTION){
+ removeItems(selected);
+ resetListModel();
+ scrollPane.validate();
+ }
+ }
+ }));
+ buttonPanel.add(removeButton);
+
+ closeButton = new JButton("Close");
+ closeButton.addActionListener(browser.wrap(new ActionListener() {
+ public void actionPerformed(ActionEvent ev) {
+ setVisible(false);
+ }
+ }));
+ buttonPanel.add(closeButton);
+ getContentPane().add(buttonPanel, "South");
+
+ pack();
+ }
+
+ public void showFrame() {
+ // init list data
+ resetListModel();
+
+ // center in parent frame
+ Rectangle bounds = ServiceEditor.this.getBounds();
+ Dimension dialogSize = getPreferredSize();
+
+ setLocation(bounds.x + (bounds.width - dialogSize.width)/ 2,
+ bounds.y + (bounds.height - dialogSize.height)/2);
+
+ setVisible(true);
+ }
+
+ private void resetListModel() {
+ //listBox.setModel(null);
+ listBox.setModel(dummyModel); // to keep away from NullException (Swing's bug)
+
+ model.removeAllElements();
+ initListModel();
+
+ listBox.setModel(model);
+ listBox.clearSelection();
+ listBox.ensureIndexIsVisible(0);
+ listBox.repaint();
+ listBox.revalidate();
+ }
+
+ protected abstract void initListModel();
+
+ protected abstract String getAddMessage();
+
+ protected abstract String getRemoveMessage(Object[] items);
+
+ protected abstract void addItems(String[] items);
+
+ protected abstract void removeItems(Object[] items);
+ }
+
+ class GroupLister extends ListerFrame {
+
+ public GroupLister(String title) {
+ super(title);
+ }
+
+ protected void initListModel() {
+ if (!(admin instanceof JoinAdmin)) {
+ return;
+ }
+
+ try {
+ String[] groups = ((JoinAdmin) admin).getLookupGroups();
+ for(int i = 0; i < groups.length; i++) {
+ model.addElement(new GroupItem(groups[i]));
+ }
+ } catch (Throwable t) {
+ logger.log(Level.INFO, "obtaining groups failed", t);
+ }
+ }
+
+ protected String getAddMessage() {
+ return "Enter adding group(s)";
+ }
+
+ protected String getRemoveMessage(Object[] items) {
+ StringBuffer msg = new StringBuffer();
+ if(items.length > 1)
+ msg.append("Remove these groups : ");
+ else
+ msg.append("Remove a group : ");
+ for(int i = 0; i < items.length; i++) {
+ if(i != 0)
+ msg.append(", ");
+ msg.append(((GroupItem) items[i]).toString());
+ }
+ return msg.toString();
+ }
+
+ protected void addItems(String[] items) {
+ // check "public"
+ String[] grps = new String[items.length];
+ for(int i = 0; i < items.length; i++)
+ grps[i] = new GroupItem(items[i]).group;
+
+ try {
+ ((JoinAdmin) admin).addLookupGroups(grps);
+ } catch (Throwable t) {
+ logger.log(Level.INFO, "adding groups failed", t);
+ }
+ }
+
+ protected void removeItems(Object[] items) {
+ String[] grps = new String[items.length];
+ for(int i = 0; i < items.length; i++)
+ grps[i] = ((GroupItem) items[i]).group;
+
+ try {
+ ((JoinAdmin) admin).removeLookupGroups(grps);
+ } catch (Throwable t) {
+ logger.log(Level.INFO, "removing groups failed", t);
+ }
+ }
+ }
+
+ class MemberGroupLister extends ListerFrame {
+
+ public MemberGroupLister(String title) {
+ super(title);
+ }
+
+ protected void initListModel() {
+ try {
+ String[] groups = ((DiscoveryAdmin) admin).getMemberGroups();
+ for(int i = 0; i < groups.length; i++) {
+ model.addElement(new GroupItem(groups[i]));
+ }
+ } catch (Throwable t) {
+ logger.log(Level.INFO, "obtaining groups failed", t);
+ }
+ }
+
+ protected String getAddMessage() {
+ return "Enter adding group(s)";
+ }
+
+ protected String getRemoveMessage(Object[] items) {
+ StringBuffer msg = new StringBuffer();
+ if(items.length > 1)
+ msg.append("Remove these groups : ");
+ else
+ msg.append("Remove a group : ");
+ for(int i = 0; i < items.length; i++) {
+ if(i != 0)
+ msg.append(", ");
+ msg.append(((GroupItem) items[i]).toString());
+ }
+ return msg.toString();
+ }
+
+ protected void addItems(String[] items) {
+ // check "public"
+ String[] grps = new String[items.length];
+ for(int i = 0; i < items.length; i++)
+ grps[i] = new GroupItem(items[i]).group;
+
+ try {
+ ((DiscoveryAdmin) admin).addMemberGroups(grps);
+ } catch (Throwable t) {
+ logger.log(Level.INFO, "adding groups failed", t);
+ }
+ }
+
+ protected void removeItems(Object[] items) {
+ String[] grps = new String[items.length];
+ for(int i = 0; i < items.length; i++)
+ grps[i] = ((GroupItem) items[i]).group;
+
+ try {
+ ((DiscoveryAdmin) admin).removeMemberGroups(grps);
+ } catch (Throwable t){
+ logger.log(Level.INFO, "removing groups failed", t);
+ }
+ }
+ }
+
+ class GroupItem {
+ public String group;
+
+ public GroupItem(String group) {
+ if(group.equals("public"))
+ this.group = "";
+ else
+ this.group = group;
+ }
+
+ public String toString() {
+ if("".equals(group))
+ return "public";
+ else
+ return group;
+ }
+ }
+
+ class LocatorLister extends ListerFrame {
+
+ public LocatorLister(String title) {
+ super(title);
+ }
+
+ protected void initListModel() {
+ if (!(admin instanceof JoinAdmin)) {
+ return;
+ }
+
+ try {
+ LookupLocator[] locators = ((JoinAdmin) admin).getLookupLocators();
+ for(int i = 0; i < locators.length; i++) {
+ model.addElement(locators[i]);
+ }
+ } catch (Throwable t) {
+ logger.log(Level.INFO, "obtaining locators failed", t);
+ }
+ }
+
+ protected String getAddMessage() {
+ return "Enter a new locator's URL";
+ }
+
+ protected String getRemoveMessage(Object[] items) {
+ StringBuffer msg = new StringBuffer();
+ if(items.length > 1)
+ msg.append("Remove these locators : ");
+ else
+ msg.append("Remove a locator : ");
+ for(int i = 0; i < items.length; i++) {
+ if(i != 0)
+ msg.append(", ");
+ msg.append(items[i].toString());
+ }
+ return msg.toString();
+ }
+
+ protected void addItems(String[] items) {
+ LookupLocator[] locs = new LookupLocator[items.length];
+ for(int i = 0; i < items.length; i++) {
+ try {
+ locs[i] = new LookupLocator(items[i]);
+ } catch (MalformedURLException e) {
+ JOptionPane.showMessageDialog(LocatorLister.this,
+ "\"" + items[i] + "\": " +
+ e.getMessage(),
+ "Bad Locator",
+ JOptionPane.WARNING_MESSAGE);
+ return;
+ }
+ }
+
+ try {
+ ((JoinAdmin) admin).addLookupLocators(locs);
+ } catch (Throwable t){
+ logger.log(Level.INFO, "adding locators failed", t);
+ }
+ }
+
+ protected void removeItems(Object[] items) {
+ LookupLocator[] locs = new LookupLocator[items.length];
+ for(int i = 0; i < items.length; i++)
+ locs[i] = (LookupLocator) items[i];
+
+ try {
+ ((JoinAdmin) admin).removeLookupLocators(locs);
+ } catch (Throwable t) {
+ logger.log(Level.INFO, "removing locators failed", t);
+ }
+ }
+ }
+
+ // provides support for ServiceUI
+ public class UIDescriptorPopup extends JPopupMenu implements ActionListener,
+ PopupMenuListener {
+
+ protected transient JMenuItem showUIItem;
+ protected transient ServiceItem serviceItem;
+
+ public UIDescriptorPopup() {
+ super();
+
+ showUIItem = new JMenuItem("Show UI");
+
+ showUIItem.addActionListener(this);
+ showUIItem.setActionCommand("showUI");
+ add(showUIItem);
+
+ addPopupMenuListener(this);
+ setOpaque(true);
+ setLightWeightPopupEnabled(true);
+ }
+
+ public void actionPerformed(ActionEvent anEvent) {
+
+ UIDescriptor uiDescriptor = getSelectedUIDescriptor();
+
+ if (uiDescriptor == null) {
+ return;
+ }
+
+ try {
+ JFrameFactory uiFactory = (JFrameFactory)
+ uiDescriptor.getUIFactory(Thread.currentThread().getContextClassLoader());
+ JFrame frame = uiFactory.getJFrame(serviceItem);
+
+ frame.validate();
+ frame.setVisible(true);
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+
+ return;
+ }
+ }
+
+ public void popupMenuWillBecomeVisible(PopupMenuEvent ev) {
+ }
+
+ public void popupMenuWillBecomeInvisible(PopupMenuEvent ev) {
+ }
+
+ public void popupMenuCanceled(PopupMenuEvent ev) {
+ }
+
+ public void setServiceItem(ServiceItem anItem) {
+ serviceItem = anItem;
+ }
+ }
+
+ class MouseReceiver extends MouseAdapter {
+
+ private ServiceEditor.UIDescriptorPopup popup;
+ private ServiceItem serviceItem;
+
+ public MouseReceiver(ServiceItem aServiceItem,
+ ServiceEditor.UIDescriptorPopup popup) {
+ this.popup = popup;
+ serviceItem = aServiceItem;
+ }
+
+ public void mouseReleased(MouseEvent ev) {
+
+ higlightSelection(ev);
+
+ if (!ev.isPopupTrigger()) {
+ return;
+ }
+
+ UIDescriptor selectedDescriptor = getSelectedUIDescriptor();
+
+ if (selectedDescriptor == null) {
+ return;
+ }
+
+ if (!"javax.swing".equals(selectedDescriptor.toolkit)) {
+ return;
+ }
+
+ popup.setServiceItem(serviceItem);
+ popup.show(ev.getComponent(), ev.getX(), ev.getY());
+ }
+
+ public void mousePressed(MouseEvent ev) {
+
+ higlightSelection(ev);
+
+ if (!ev.isPopupTrigger()) {
+ return;
+ }
+
+ UIDescriptor selectedDescriptor = getSelectedUIDescriptor();
+
+ if (selectedDescriptor == null) {
+ return;
+ }
+
+ if (!"javax.swing".equals(selectedDescriptor.toolkit)) {
+ return;
+ }
+
+ popup.setServiceItem(serviceItem);
+ popup.show(ev.getComponent(), ev.getX(), ev.getY());
+ }
+ }
+
+ private UIDescriptor getSelectedUIDescriptor() {
+
+ ObjectNode selectedNode =
+ (ObjectNode) attrPanel.tree.getLastSelectedPathComponent();
+
+ if (selectedNode == null) {
+ return null;
+ }
+
+ Object selectedObject = selectedNode.getObject();
+
+ try {
+ return (UIDescriptor) selectedObject;
+ }
+ catch (ClassCastException e) {
+ return null;
+ }
+ }
+
+ private void higlightSelection(MouseEvent event) {
+ attrPanel.tree.setSelectionPath(attrPanel.tree.getPathForLocation(
+ event.getX(), event.getY()));
+ }
+
+ private ServiceEditor.UIDescriptorPopup uiDescriptorPopup() {
+ return new ServiceEditor.UIDescriptorPopup();
+ }
+}
diff --git a/browser/src/main/java/org/apache/river/container/examples/browser/package.html b/browser/src/main/java/org/apache/river/container/examples/browser/package.html
new file mode 100644
index 0000000..50eb836
--- /dev/null
+++ b/browser/src/main/java/org/apache/river/container/examples/browser/package.html
@@ -0,0 +1,787 @@
+<!--
+ ! Licensed to the Apache Software Foundation (ASF) under one
+ ! or more contributor license agreements. See the NOTICE file
+ ! distributed with this work for additional information
+ ! regarding copyright ownership. The ASF licenses this file
+ ! to you under the Apache License, Version 2.0 (the
+ ! "License"); you may not use this file except in compliance
+ ! with the License. You may obtain a copy of the License at
+ !
+ ! http://www.apache.org/licenses/LICENSE-2.0
+ !
+ ! Unless required by applicable law or agreed to in writing, software
+ ! distributed under the License is distributed on an "AS IS" BASIS,
+ ! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ! See the License for the specific language governing permissions and
+ ! limitations under the License.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN"
+ "http://www.w3.org/TR/1998/REC-html40-19980424/strict.dtd">
+<html>
+<head>
+<title>Service Browser</title>
+</head>
+<body text="#000000" bgcolor="#ffffff" link="#9b37cc"
+ vlink="#cc1877" alink="#ffffff">
+A visualization tool for exploring Jini Network Technology
+communities (<i>djinns</i>) with support for ServiceUI. The Service Browser
+lets you discover lookup services and inspect the various
+services registered within those lookup services. This tool
+is provided as an example program within the Apache River release
+but many who use it think
+of it more as a utility than an example.
+<p>
+This document provides the following information about the Service Browser:
+<ul>
+<li><a href="#running">Running the Service Browser</a>
+<li><a href="#discovering">Discovering and Selecting a Lookup Service</a>
+<li><a href="#inspecting">Inspecting Services</a>
+<li><a href="#filtering">Filtering Services</a>
+<li><a href="#miscops">Other Operations</a>
+<li><a href="#configuring">Configuring the Service Browser</a>
+<li><a href="#logging">Loggers and Logging Levels</a>
+<li><a href="#examples">Examples for Running the Service Browser</a>
+</ul>
+
+<a name="running"></a>
+<h2>Running the Service Browser</h2>
+The Service Browser is started with the following command line
+structure:
+<blockquote><pre>
+% java <var><b>java_options</b></var>
+ -jar <var><b>install_dir</b></var>/lib/browser.jar
+ <var><b>config_options</b></var>
+</pre></blockquote>
+where <var><b>java_options</b></var> includes such things as the security
+policy file and the RMI codebase annotation, <var><b>install_dir</b></var>
+is the directory where the Apache River release is installed, and
+<var><b>config_options</b></var> includes any
+{@link net.jini.config.Configuration} provider options. The
+Service Browser can be run with no <var><b>config_options</b></var>.
+For the RMI codebase annotation,
+the system property <code>java.rmi.server.codebase</code> should be set to
+two URLs that provide HTTP-based access to the Service Browser's download
+JAR files,
+<code><var><b>install_dir</b></var>/lib-dl/browser-dl.jar</code> and
+<code><var><b>install_dir</b></var>/lib-dl/jsk-dl.jar</code>, in that order.
+<p>
+The Service Browser also can be run as a nonactivatable entity under the
+{@linkplain com.sun.jini.start Service Starter}, using
+<code>com.sun.jini.example.browser.Browser</code> for the implementation
+class, <code><var><b>install_dir</b></var>/lib/browser.jar</code> for the
+implementation class path, and a codebase containing URLs for
+<code><var><b>install_dir</b></var>/lib-dl/browser-dl.jar</code> and
+<code><var><b>install_dir</b></var>/lib-dl/jsk-dl.jar</code> (in that order).
+
+<a name="discovering"></a>
+<h2>Discovering and Selecting a Lookup Service</h2>
+The lookup services that will be discovered are initially controlled
+by configuration entries, with the default being to perform multicast
+discovery for all lookup services. If you specify one or more groups or
+addresses in a configuration file (as described in
+<a href="#configuring">Configuring the Service Browser</a>),
+the browser only discovers lookup services that are members of those groups
+or that are at those addresses.
+<p>
+The Service Browser presents a single window when it is started. The
+window has the menu items <b>File</b>, <b>Registrar</b>, <b>Options</b>,
+<b>Services</b>, and <b>Attributes</b>. The upper text area changes to
+show which groups and/or addresses are being used for discovery and
+how many lookup services have been discovered.
+The picture below shows a typical Service Browser main window, in which
+nine lookup services have been discovered, but none have yet been selected.
+<p>
+<img src="doc-files/browser1.gif"
+Alt="Picture of the lookup browser as it looks when it first starts.">
+<p>
+As lookup services are discovered,
+they appear under the <b>Registrar</b> menu, listed by unicast address.
+A specific lookup service can then be selected from this menu for browsing.
+When a lookup service is selected, the upper text area will be revised to
+show the number of services registered in that lookup service, and the lower
+folder area will show the registered services. Administrable services
+are shown with a blue icon, and non-administrable services are shown with
+a gray icon. As new services register or existing services unregister,
+the contents of the window will change automatically to reflect them.
+<p>
+After initial start up, different lookup services can be discovered
+using several items available from the <b>File</b> menu:
+<ul>
+ <li><b>Find All</b> will perform multicast discovery for all lookup
+ services. This is the default behavior of the
+ browser when it first starts.</li>
+ <li><b>Find Public</b> will perform multicast discovery for all lookup
+ services that are members of the <code>public</code> group.</li>
+ <li><b>Find By Group...</b> will present a dialog box allowing you to
+ specify one or more group names (separated by commas or whitespace).
+ Multicast discovery will then be performed to find all lookup
+ services that are members of those groups.</li>
+ <li><b>Find By Address...</b> will present a dialog box allowing you to
+ specify one or more unicast addresses (separated by commas or
+ whitespace). Unicast discovery will then be performed to find the
+ lookup services at those adddresses.</li>
+</ul>
+<p>
+For example, the picture below shows the dialog box presented when the
+<b>Find By Group...</b> menu item is selected.
+<p>
+<img src="doc-files/browser2.gif"
+Alt="Picture of the lookup browser with File menu pulled down.">
+<p>
+In this next picture, the group name <code>examples.jini.sun.com</code>
+will be entered.
+<p>
+<img src="doc-files/browser3.gif"
+Alt="Picture of the lookup browser with group dialog box visible.">
+<p>
+After the group <code>examples.jini.sun.com</code> has been entered, a list of
+discovered lookup services will appear in the <b>Registrar</b> menu.
+In this example, only the host
+<code>kahlua</code> has a lookup service running that is a member of
+<code>examples.jini.sun.com</code>, so <code>kahlua</code> is the only
+address in the drop-down list from the <b>Registrar</b> menu.
+<p>
+<img src="doc-files/browser4.gif"
+Alt="Picture of the lookup browser with drop-down registrar list visible.">
+<p>
+When <code>kahlua</code> is selected from the <b>Registrar</b>
+menu, the window contents change as shown in the next picture.
+In this example, six services are registered:
+<p>
+<img src="doc-files/browser5.gif"
+Alt="Picture of the lookup browser with six services visible.">
+
+<a name="inspecting"></a>
+<h2>Inspecting Services</h2>
+Double clicking on a service listed in the lower area of the main window
+will open a new window, which can be used to inspect and optionally edit
+attributes of the selected service. The window is titled <b>ServiceItem
+Editor</b> if the service's administrative proxy implements any of the
+administrative interfaces supported by the Service Browser, otherwise it is
+titled <b>ServiceItem Browser</b>. In our running example,
+double clicking on the <b>net.jini.core.lookup.ServiceRegistrar</b>
+item opens this window:
+<p>
+<img src="doc-files/browser6.gif"
+Alt="Picture of the ServiceItem editor when first opened.">
+<p>
+The window shows all of the attribute sets
+that can be inspected and, in some cases, altered. Attribute sets and
+attributes with blue icons can be edited, those with gray icons cannot.
+An attribute set can be edited if it is not
+{@link net.jini.lookup.entry.ServiceControlled} and the service's
+administrative proxy implements the {@link net.jini.admin.JoinAdmin} interface.
+Within an editable attribute set, an attribute can be edited if it is of
+type <code>String</code> or one of the primitive wrapper classes.
+The two attribute sets shown in the example above are <b>ServiceInfo</b>
+and <b>BasicServiceType</b>.
+Each of these entries can be expanded by double-clicking on them
+as illustrated in the next two pictures.
+<p>
+Expanding the <b>BasicServiceType</b> entry:
+<p>
+<img src="doc-files/browser7.gif"
+Alt="Picture of the ServiceItem editor with BasicServiceType expanded.">
+<p>
+Expanding the <b>ServiceInfo</b> entry:
+<p>
+<img src="doc-files/browser8.gif"
+Alt="Picture of the ServiceItem editor with all items expanded.">
+<p>
+Double clicking again on an entry collapses the view. In an expanded entry,
+double clicking on an editable attribute brings up a dialog box that
+allows you to input a new value for the attribute. You can also use
+the <b>Edit</b> menu in the <b>ServiceItem Editor</b> window to
+add and remove attribute sets, if the service's administrative proxy
+implements the {@link net.jini.admin.JoinAdmin} interface.
+Select <b>Add attribute...</b> from the <b>Edit</b> menu to add an
+attribute set. (Note that for this to work, you will have to run the
+Service Browser in such a way that the desired attribute set classes
+are in both its class path and its codebase.)
+Click on an attribute set and then select <b>Remove attribute</b> from
+the <b>Edit</b> menu to delete an attribute set.
+
+<a name="filtering"></a>
+<h2>Filtering Services</h2>
+Back in the main window, the <b>Services</b> menu shows various types that
+the registered services are instances of. You can select one or more of
+these types to add them your current template to refine the set of
+services displayed in the lower area of the window. When you select a
+type, it appears in the upper text area to show you what your new
+template contains, the revised number of matching services is shown,
+and only the services that are instances of all of the selected types are
+displayed in the lower area.
+<p>
+Similarly, the <b>Attributes</b> menu shows attribute set classes (with
+package prefixes removed) that can be added to your current template to
+refine the set of services.
+When you select a class, it appears in the upper text area to
+show you what your new template contains, the revised number of
+matching services is shown, and only the services with such an attribute
+set are displayed in the lower area. In addition, the selected menu item
+changes to a pull-aside menu.
+You can go back to the <b>Attributes</b> menu to select
+additional attribute set classes, or traverse a pull-aside menu to
+see the names of attributes, each of which is itself a pull-aside
+menu. You can traverse these to see the possible values for the
+attributes, and select one to further refine your search. You
+can deselect existing attribute choices, and you can deselect
+the <code>(match)</code> menu item to deselect an entire class.
+<p>
+By default, the <b>Services</b> menu only shows service interface types. You
+can select <b>Service classes</b> from the <b>Options</b> menu to see service
+implementation classes instead. By default, only the most-derived interfaces
+or classes are shown, but you can select <b>Service supertypes</b> from the
+<b>Options</b> menu to see supertypes as well. Similarly, by default only the
+most-derived attribute set classes are shown in the <b>Attributes</b>
+menu, but you can select <b>Attribute supertypes</b> from the
+<b>Options</b> menu to see superclasses as well.
+<p>
+Select <b>Reset</b> from the <b>File</b> menu of the main window
+to reset the template to the initial (empty) state.
+
+<a name="miscops"></a>
+<h2>Other Operations</h2>
+In the lower area of the main window, you can right click on a service
+to obtain a popup menu. Select <b>Show Info</b> from this menu to see
+details about service ID and service types. If the service's administrative
+proxy implements any of the administrative interfaces supported by the Service
+Browser, selecting <b>Admin Service</b> from this menu brings up the
+<b>ServiceItem Editor</b> window. Selecting <b>Browse Service</b> from
+this menu brings up the <b>ServiceItem Browser</b> window. If the service
+is a JavaSpaces service and either its proxy implements the
+{@link net.jini.space.JavaSpace05} interface or its administrative proxy
+implements the {@link com.sun.jini.outrigger.JavaSpaceAdmin}
+interface, selecting <b>Browse Entries</b> from this menu brings up a
+window that displays all of the current entries.
+<p>
+In a <b>ServiceItem Editor</b> window, the <b>Admin</b> menu allows
+various administrative operations to be performed on the service.
+If the service's administrative proxy implements the
+{@link net.jini.admin.JoinAdmin} interface,
+you can use the <b>Joining groups...</b> and <b>Joining locators...</b>
+menu items to control which lookup groups and lookup locators the
+service uses for lookup discovery and join.
+If the service is itself a lookup service and its administrative proxy
+implements the {@link net.jini.lookup.DiscoveryAdmin} interface, you can use
+the <b>Member groups...</b> menu item to control
+which groups the lookup service is a member of, and you can
+use the <b>Unicast port...</b> menu item to control
+which unicast port the lookup service uses for its lookup locator.
+If the service's administrative proxy implements the
+{@link com.sun.jini.admin.DestroyAdmin}
+interface, you can use the <b>Destroy</b> menu item to destroy the
+service. This last menu selection is illustrated in the picture below.
+<p>
+<img src="doc-files/browser9.gif"
+Alt="Picture of the ServiceItem editor with Destroy selected from the Admin menu.">
+
+<a name="configuring"></a>
+<h2>Configuring the Service Browser</h2>
+
+The Service Browser obtains its configuration by calling
+{@link net.jini.config.ConfigurationProvider#getInstance(String[],ClassLoader)
+ConfigurationProvider.getInstance} with the <var><b>config_options</b></var>
+specified on the command line and the class loader for the main
+implementation class.
+<p>
+The Service Browser supports the following configuration
+entries, with component <code>com.sun.jini.example.browser</code>:
+<p>
+
+<a name="adminPreparer"></a>
+<table summary="Describes the adminPreparer configuration entry"
+ border="0" cellpadding="2">
+ <tr valign="top">
+ <th scope="col" summary="layout"> <font size="+1">•</font>
+ <th scope="col" align="left" colspan="2"> <font size="+1"><code>
+ adminPreparer</code></font>
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Type: <td> {@link net.jini.security.ProxyPreparer}
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Default: <td> <code>
+ new {@link net.jini.security.BasicProxyPreparer}()
+ </code>
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Description: <td> Prepares administrative proxies obtained by calling
+ the {@link net.jini.admin.Administrable#getAdmin Administrable.getAdmin}
+ method on service proxies. The value should not be <code>null</code>.
+ The Service Browser only calls methods on an administrative proxy if
+ administrative operations are performed using menu items. Methods of
+ the {@link net.jini.admin.JoinAdmin},
+ {@link net.jini.lookup.DiscoveryAdmin},
+ {@link com.sun.jini.admin.DestroyAdmin}, and
+ {@link com.sun.jini.outrigger.JavaSpaceAdmin} interfaces can be invoked.
+</table>
+
+<a name="discoveryManager"></a>
+<table summary="Describes the discoveryManager configuration entry"
+ border="0" cellpadding="2">
+ <tr valign="top">
+ <th scope="col" summary="layout"> <font size="+1">•</font>
+ <th scope="col" align="left" colspan="2"> <font size="+1"><code>
+ discoveryManager</code></font>
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Type: <td> {@link net.jini.discovery.DiscoveryManagement}
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Default: <td>
+<pre>
+new {@link net.jini.discovery.LookupDiscoveryManager}(
+ {@link net.jini.discovery.DiscoveryGroupManagement#NO_GROUPS},
+ null, // locators
+ null, // listener
+ this) // config
+</pre>
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Description: <td> Supplies the object used to discover lookup services.
+ The value should not be <code>null</code>. The object returned must
+ also implement {@link net.jini.discovery.DiscoveryGroupManagement} and
+ {@link net.jini.discovery.DiscoveryLocatorManagement}, and should be
+ configured initially to discover no groups and no specific lookup
+ services.
+</table>
+
+<a name="exitActionListener"></a>
+<table summary="Describes the exitActionListener configuration entry"
+ border="0" cellpadding="2">
+ <tr valign="top">
+ <th scope="col" summary="layout"> <font size="+1">•</font>
+ <th scope="col" align="left" colspan="2"> <font size="+1"><code>
+ exitActionListener</code></font>
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Type: <td> {@link java.awt.event.ActionListener}
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Default: <td> see the {@link com.sun.jini.example.browser.Browser}
+ constructors for details
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Description: <td> The action listener for the <b>Exit</b> item of
+ the <b>File</b> menu.
+</table>
+
+<a name="folderView"></a>
+<table summary="Describes the folderView configuration entry"
+ border="0" cellpadding="2">
+ <tr valign="top">
+ <th scope="col" summary="layout"> <font size="+1">•</font>
+ <th scope="col" align="left" colspan="2"> <font size="+1"><code>
+ folderView</code></font>
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Type: <td> <code>boolean</code>
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Default: <td> <code>true</code>
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Description: <td> Controls the format of the main window.
+ If <code>true</code>, the main window is split into two areas
+ as described in the rest of this documentation. If <code>false</code>,
+ the main window a single text area, and many of the browsing and
+ administrative operations are not available; the <b>Show Matches</b>
+ item of the <b>File</b> menu must be used to display services
+ registered in the selected lookup service.
+</table>
+
+<a name="initialLookupGroups"></a>
+<table summary="Describes the initialLookupGroups configuration entry"
+ border="0" cellpadding="2">
+ <tr valign="top">
+ <th scope="col" summary="layout"> <font size="+1">•</font>
+ <th scope="col" align="left" colspan="2"> <font size="+1"><code>
+ initialLookupGroups</code></font>
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Type: <td> <code>String[]</code>
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Default: <td> <code>null</code>
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Description: <td> The names of the lookup groups that the Service
+ Browser should initially discover. An empty array specifies that no groups
+ should be discovered. A <code>null</code> value specifies that all
+ lookup services should be discovered. Otherwise, the value specifies the
+ names of the groups to discover. The array must not contain
+ <code>null</code> elements. This entry used when the Service Browser is
+ first started; after initial startup, lookup service discovery is
+ controlled through the <b>File</b> menu.
+</table>
+
+<a name="initialLookupLocators"></a>
+<table summary="Describes the initialLookupLocators configuration entry"
+ border="0" cellpadding="2">
+ <tr valign="top">
+ <th scope="col" summary="layout"> <font size="+1">•</font>
+ <th scope="col" align="left" colspan="2"> <font size="+1"><code>
+ initialLookupLocators</code></font>
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Type: <td> <code>{@link net.jini.core.discovery.LookupLocator}[]</code>
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Default: <td> <code>null</code>
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Description: <td> The locators for specific lookup services that
+ the Service Browser should initially discover. An empty array or
+ <code>null</code> specifies that no specific lookup services
+ should be discovered. The array must not contain <code>null</code>
+ elements. This entry is used when the Service Browser is first started;
+ after initial startup, lookup service discovery is controlled through
+ the <b>File</b> menu.
+ <p>
+ Note that the constraints specified in the <code>locatorConstraints</code>
+ configuration entry will not be used for these initial locators; any
+ desired constraints should be attached directly to the initial locators.
+</table>
+
+<a name="leaseManager"></a>
+<table summary="Describes the leaseManager configuration entry"
+ border="0" cellpadding="2">
+ <tr valign="top">
+ <th scope="col" summary="layout"> <font size="+1">•</font>
+ <th scope="col" align="left" colspan="2"> <font size="+1"><code>
+ leaseManager</code></font>
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Type: <td> {@link net.jini.lease.LeaseRenewalManager}
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Default: <td> <code>new LeaseRenewalManager(this /* config */)</code>
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Description: <td> Supplies the lease renewal manager used to
+ renew leases for remote event registrations and when obtaining the
+ entries of a JavaSpaces service. The value should not be
+ <code>null</code>.
+</table>
+
+<a name="leasePreparer"></a>
+<table summary="Describes the leasePreparer configuration entry"
+ border="0" cellpadding="2">
+ <tr valign="top">
+ <th scope="col" summary="layout"> <font size="+1">•</font>
+ <th scope="col" align="left" colspan="2"> <font size="+1"><code>
+ leasePreparer</code></font>
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Type: <td> {@link net.jini.security.ProxyPreparer}
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Default: <td> <code>
+ new {@link net.jini.security.BasicProxyPreparer}()
+ </code>
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Description: <td> Prepares leases obtained from registering
+ for event notifications with lookup services and from requesting
+ the entries of a JavaSpaces service. The value should not be
+ <code>null</code>.
+ <p>
+ The Service Browser calls the {@link net.jini.core.lease.Lease#renew
+ renew} and {@link net.jini.core.lease.Lease#cancel cancel} methods on
+ leases, and the {@link net.jini.core.lease.LeaseMap#renewAll renewAll}
+ method on lease maps created (via the {@link
+ net.jini.lease.LeaseRenewalManager}) from leases.
+</table>
+
+<a name="listenerExporter"></a>
+<table summary="Describes the listenerExporter configuration entry"
+ border="0" cellpadding="2">
+ <tr valign="top">
+ <th scope="col" summary="layout"> <font size="+1">•</font>
+ <th scope="col" align="left" colspan="2"> <font size="+1"><code>
+ listenerExporter</code></font>
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Type: <td> {@link net.jini.export.Exporter}
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Default: <td>
+<pre>
+new {@link net.jini.jeri.BasicJeriExporter}(
+ {@link net.jini.jeri.tcp.TcpServerEndpoint#getInstance TcpServerEndpoint.getInstance}(0),
+ new {@link net.jini.jeri.BasicILFactory}(), false, false)
+</pre>
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Description: <td> The object to use for exporting all
+ {@link net.jini.core.event.RemoteEventListener} instances used
+ by the Service Browser when registering for event notifications from
+ lookup services. The value should not be <code>null</code>.
+</table>
+
+<a name="locatorConstraints"></a>
+<table summary="Describes the locatorConstraints configuration entry"
+ border="0" cellpadding="2">
+ <tr valign="top">
+ <th scope="col" summary="layout"> <font size="+1">•</font>
+ <th scope="col" align="left" colspan="2"> <font size="+1"><code>
+ locatorConstraints</code></font>
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Type: <td> {@link net.jini.core.constraint.MethodConstraints}
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Default: <td> <code>null</code>
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Description: <td> Constraints to attach to any
+ {@link net.jini.core.discovery.LookupLocator} instances constructed
+ from the addresses entered using the <b>Find By Address...</b> menu item.
+ <p>
+ These constraints are not attached to the locators obtained from the
+ <code>initialLookupLocators</code> configuration entry.
+</table>
+
+<a name="loginContext"></a>
+<table summary="Describes the loginContext configuration entry"
+ border="0" cellpadding="2">
+ <tr valign="top">
+ <th scope="col" summary="layout"> <font size="+1">•</font>
+ <th scope="col" align="left" colspan="2"> <font size="+1"><code>
+ loginContext</code></font>
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Type: <td> {@link javax.security.auth.login.LoginContext}
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Default: <td> <code>null</code>
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Description: <td> Specifies the JAAS login context to use for
+ performing a JAAS login and supplying the
+ {@link javax.security.auth.Subject} to use when running the
+ Service Browser. If <code>null</code>, no JAAS login is performed
+ and no subject is used.
+</table>
+
+<a name="servicePreparer"></a>
+<table summary="Describes the servicePreparer configuration entry"
+ border="0" cellpadding="2">
+ <tr valign="top">
+ <th scope="col" summary="layout"> <font size="+1">•</font>
+ <th scope="col" align="left" colspan="2"> <font size="+1"><code>
+ servicePreparer</code></font>
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Type: <td> {@link net.jini.security.ProxyPreparer}
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Default: <td> <code>
+ new {@link net.jini.security.BasicProxyPreparer}()
+ </code>
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Description: <td> Prepares service proxies obtained from lookup servics.
+ The value should not be <code>null</code>. The Service Browser
+ calls the {@link net.jini.admin.Administrable#getAdmin
+ Administrable.getAdmin} method on a service proxy for most
+ administrative operations, and calls the
+ {@link net.jini.space.JavaSpace05#contents JavaSpace05.contents}
+ method on a JavaSpaces service proxy to browse entries.
+</table>
+
+<a name="uninterestingInterfaces"></a>
+<table summary="Describes the uninterestingInterfaces configuration entry"
+ border="0" cellpadding="2">
+ <tr valign="top">
+ <th scope="col" summary="layout"> <font size="+1">•</font>
+ <th scope="col" align="left" colspan="2"> <font size="+1"><code>
+ uninterestingInterfaces</code></font>
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Type: <td> <code>String[]</code>
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Default: <td>
+<pre>
+new String[]{
+ "java.io.Serializable",
+ "java.rmi.Remote",
+ "net.jini.admin.Administrable",
+ "net.jini.core.constraint.RemoteMethodControl",
+ "net.jini.id.ReferentUuid"
+ "net.jini.security.proxytrust.TrustEquivalence"}
+</pre>
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Description: <td> Supplies the set of interface names that are not
+ interesting to choose when displaying service instances the lower
+ area of the main window. If the service proxy implements exactly
+ one interface that is not in this list, the name of that interface
+ is used to name the service, otherwise the concrete class of the
+ service proxy is used appended with all the names of the 'interesting'
+ interfaces implemented by that proxy. The value should not be
+ <code>null</code>.
+</table>
+
+<a name="autoConfirm"></a>
+<table summary="Describes the autoConfirm configuration entry"
+ border="0" cellpadding="2">
+ <tr valign="top">
+ <th scope="col" summary="layout"> <font size="+1">•</font>
+ <th scope="col" align="left" colspan="2"> <font size="+1"><code>
+ autoConfirm</code></font>
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Type: <td> <code>boolean</code>
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Default: <td> <code>false</code>
+ <tr valign="top"> <td>   <th scope="row" align="right">
+ Description: <td> Controls whether a confirmation window must popup that
+ requires a confirmation for the update or removal of the service editor
+ window. In case set to <code>true</code> closing of the service editor
+ window will occur automatically in case a service is removed, in case of
+ modifications to the lookup attributes of a service the service editor
+ window will be updated.
+</table>
+<p>
+Note that additional entries can also be specified to configure the
+{@link net.jini.lease.LeaseRenewalManager},
+{@link net.jini.discovery.LookupDiscovery}, and
+{@link net.jini.discovery.LookupLocatorDiscovery} components. For
+example, the Service Browser assumes you have configured the discovery
+manager (obtained from the <code>discoveryManager</code> configuration
+entry) to enforce any desired constraints for multicast discovery
+and to perform any desired proxy preparation for all discovered lookup
+services.
+<p>
+The most commonly used configuration entries are likely to be:
+<ul>
+<li><a href="#initialLookupGroups"><code>initialLookupGroups</code></a>
+<li><a href="#initialLookupLocators"><code>initialLookupLocators</code></a>
+</ul>
+<p>
+Secure deployments typically will provide values for most of the following
+configuration entries:
+<ul>
+<li><a href="#loginContext"><code>loginContext</code></a>
+<li><a href="#leasePreparer"><code>leasePreparer</code></a>
+<li><a href="#servicePreparer"><code>servicePreparer</code></a>
+<li><a href="#adminPreparer"><code>adminPreparer</code></a>
+<li><a href="#listenerExporter"><code>listenerExporter</code></a>
+<li><a href="#locatorConstraints"><code>locatorConstraints</code></a>
+</ul>
+plus the following configuration entries for <code>LookupDiscovery</code>
+(if groups are used for discovery):
+<ul>
+<li><code>registrarPreparer</code></li>
+<li><code>discoveryConstraints</code></li>
+</ul>
+and the following configuration entry for <code>LookupLocatorDiscovery</code>
+(if locators are used for discovery):
+<ul>
+<li><code>registrarPreparer</code></li>
+</ul>
+
+<a name="logging"></a>
+<h2>Loggers and Logging Levels</h2>
+
+The Service Browser uses the {@link java.util.logging.Logger} named
+<code>com.sun.jini.example.browser</code> to log
+information at the following logging levels:
+<p>
+<table border="1" cellpadding="5"
+ summary="Describes logging performed by Service Browser at different
+ logging levels">
+
+<caption halign="center" valign="top"><b><code>
+ com.sun.jini.example.browser</code></b></caption>
+
+<tr> <th scope="col"> Level <th scope="col"> Description
+
+<tr> <td> {@link java.util.logging.Level#SEVERE SEVERE} <td>
+Problems that prevent Service Browser startup
+
+<tr> <td> {@link java.util.logging.Level#INFO INFO} <td> Potentially
+interesting exceptions that occur when performing operations
+
+<tr> <td> {@link com.sun.jini.logging.Levels#HANDLED HANDLED} <td>
+Less interesting exceptions that occur when performing operations
+
+</table>
+<p>
+See the {@link com.sun.jini.logging.LogManager} class for one way to use the
+<code>HANDLED</code> logging level in standard logging configuration files.
+
+<a name="examples"></a>
+<h2>Examples for Running the Service Browser</h2>
+
+An example command line for starting the Service Browser is:
+<blockquote><pre>
+% java -Djava.security.policy=<a href="#policy">browser.policy</a>
+ -Djava.rmi.server.codebase="http://<var><b>your_host</b></var>:<var><b>http_port</b></var>/browser-dl.jar http://<var><b>your_host</b></var>:<var><b>http_port</b></var>/jsk-dl.jar"
+ -Djava.protocol.handler.pkgs=net.jini.url
+ -jar <var><b>install_dir</b></var>/lib/browser.jar
+</pre></blockquote>
+In this example, the local host is assumed to have the name
+<var><b>your_host</b></var>, running an HTTP server on port
+<var><b>http_port</b></var>, serving files in the <code>lib</code>
+subdirectory of <var><b>install_dir</b></var>, the directory where the
+Apache River release is installed.
+When the Service Browser is run this way, without a configuration file, it
+will perform multicast discovery to find all available lookup
+services, and will use Jini extensible remote invocation (Jini ERI)
+over TCP/IP for its remote event listeners.
+<p>
+An example command line for starting the Service Browser with a
+configuration file is:
+<blockquote><pre>
+% java -Djava.security.policy=<a href="#policy">browser.policy</a>
+ -Djava.rmi.server.codebase="http://<var><b>your_host</b></var>:<var><b>http_port</b></var>/browser-dl.jar http://<var><b>your_host</b></var>:<var><b>http_port</b></var>/jsk-dl.jar"
+ -Djava.protocol.handler.pkgs=net.jini.url
+ -jar <var><b>install_dir</b></var>/lib/browser.jar
+ <a href="#public">public.config</a>
+</pre></blockquote>
+The example configuration file <code>public.config</code> only discovers
+lookup services in the public group:
+<a name="public"></a>
+<blockquote><pre>
+com.sun.jini.example.browser {
+ initialLookupGroups = new String[]{""};
+}
+</pre></blockquote>
+<p>
+The following example configuration file initially discovers no
+lookup services and uses JRMP for its remote event listeners:
+<blockquote><pre>
+com.sun.jini.example.browser {
+ initialLookupGroups = new String[]{};
+ listenerExporter = new net.jini.jrmp.JrmpExporter();
+}
+</pre></blockquote>
+<p>
+In all the above command lines, the policy file <code>browser.policy</code>
+was specified for the <code>java.security.policy</code> system property.
+An example policy file is:
+<a name="policy"></a>
+<blockquote><pre>
+grant codebase "file:<var><b>install_dir</b></var>/lib/browser.jar" {
+ permission java.security.AllPermission;
+};
+
+grant codebase "file:<var><b>install_dir</b></var>/lib/jsk-platform.jar" {
+ permission java.security.AllPermission;
+};
+
+grant codebase "file:<var><b>install_dir</b></var>/lib/jsk-lib.jar" {
+ permission java.security.AllPermission;
+};
+
+grant {
+ permission java.net.SocketPermission "*:1024-", "connect";
+};
+</pre></blockquote>
+<p>
+<b>Note:</b> Of course, a more restrictive policy file could be used;
+the one shown here is for illustrative purposes only.
+<p>
+An example of running under the Service Starter is:
+<blockquote><pre>
+% java -Djava.security.policy=<a href="#start_policy">start.policy</a>
+ -Djava.protocol.handler.pkgs=net.jini.url
+ -jar <var><b>install_dir</b></var>/lib/start.jar
+ <a href="#start_config">start.config</a>
+</pre></blockquote>
+<p>
+where <code>start.config</code> is the following configuration file:
+<a name="start_config"></a>
+<blockquote><pre>
+import com.sun.jini.start.NonActivatableServiceDescriptor;
+import com.sun.jini.start.ServiceDescriptor;
+
+com.sun.jini.start {
+
+ serviceDescriptors = new ServiceDescriptor[]{
+ new NonActivatableServiceDescriptor(
+ "http://<var><b>your_host</b></var>:<var><b>http_port</b></var>/browser-dl.jar http://<var><b>your_host</b></var>:<var><b>http_port</b></var>/jsk-dl.jar",
+ "<a href="#policy">browser.policy</a>",
+ "<var><b>install_dir</b></var>/lib/browser.jar",
+ "com.sun.jini.example.browser.Browser",
+ new String[]{"<a href="#public">public.config</a>"})
+ };
+}
+</pre></blockquote>
+An example policy file for <code>start.policy</code> is:
+<a name="start_policy"></a>
+<blockquote><pre>
+grant codebase "file:<var><b>install_dir</b></var>/lib/start.jar" {
+ permission java.security.AllPermission;
+};
+
+grant codebase "file:<var><b>install_dir</b></var>/lib/jsk-platform.jar" {
+ permission java.security.AllPermission;
+};
+</pre></blockquote>
+</body>
+</html>
diff --git a/browser/src/test/java/org/apache/river/container/browser/AppTest.java b/browser/src/test/java/org/apache/river/container/browser/AppTest.java
new file mode 100644
index 0000000..f1d7161
--- /dev/null
+++ b/browser/src/test/java/org/apache/river/container/browser/AppTest.java
@@ -0,0 +1,38 @@
+package org.apache.river.container.browser;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Unit test for simple App.
+ */
+public class AppTest
+ extends TestCase
+{
+ /**
+ * Create the test case
+ *
+ * @param testName name of the test case
+ */
+ public AppTest( String testName )
+ {
+ super( testName );
+ }
+
+ /**
+ * @return the suite of tests being tested
+ */
+ public static Test suite()
+ {
+ return new TestSuite( AppTest.class );
+ }
+
+ /**
+ * Rigourous Test :-)
+ */
+ public void testApp()
+ {
+ assertTrue( true );
+ }
+}
diff --git a/pom.xml b/pom.xml
index b0cfd61..0b0f4ad 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,7 +11,8 @@
<module>test-container</module>
<module>reggie-module</module>
<module>browser-module</module>
- </modules>
+ <module>browser</module>
+ </modules>
<build>
<pluginManagement>
diff --git a/river-container-core/src/main/java/org/apache/river/container/deployer/StarterServiceDeployer.java b/river-container-core/src/main/java/org/apache/river/container/deployer/StarterServiceDeployer.java
index 46911e3..d98ddd1 100644
--- a/river-container-core/src/main/java/org/apache/river/container/deployer/StarterServiceDeployer.java
+++ b/river-container-core/src/main/java/org/apache/river/container/deployer/StarterServiceDeployer.java
@@ -79,7 +79,6 @@
private String myName = null;
@Injected(style = InjectionStyle.BY_TYPE)
private CodebaseHandler codebaseHandler = null;
-
private String config = Strings.STARTER_SERVICE_DEPLOYER_CONFIG;
private ASTconfig configNode = null;
@@ -92,23 +91,19 @@
}
@Injected(style = InjectionStyle.BY_TYPE)
private PropertiesFileReader propertiesFileReader = null;
-
@Injected(style = InjectionStyle.BY_TYPE)
private ArgsParser argsParser = null;
-
@Injected(style = InjectionStyle.BY_TYPE)
WorkManager workManager = null;
-
- @Injected(style=InjectionStyle.BY_TYPE)
- ContextualWorkManager contextualWorkManager=null;
-
+ @Injected(style = InjectionStyle.BY_TYPE)
+ ContextualWorkManager contextualWorkManager = null;
@Injected(style = InjectionStyle.BY_TYPE)
private DynamicPolicyProvider securityPolicy = null;
public void addPlatformCodebaseJars(CodebaseContext codebaseContext) throws IOException {
ASTcodebase codebaseNode = (ASTcodebase) configNode.search(new Class[]{
- ASTconfig.class, ASTclassloader.class, ASTcodebase.class
- }).get(0);
+ ASTconfig.class, ASTclassloader.class, ASTcodebase.class
+ }).get(0);
/*
Register the platform codebase jars with the codebase service.
*/
@@ -143,7 +138,7 @@
Include platform jars from the container's lib directory.
*/
ASTclasspath platformJarSpec = (ASTclasspath) configNode.search(new Class[]{ASTconfig.class,
- ASTclassloader.class, ASTjars.class, ASTclasspath.class}).get(0);
+ ASTclassloader.class, ASTjars.class, ASTclasspath.class}).get(0);
addPlatformJarsToClassloader(platformJarSpec, cl);
addLibDirectoryJarsToClasspath(serviceRoot, cl);
@@ -184,10 +179,13 @@
Register the service's codebase jars with the codebase service.
*/
FileObject libDlDir = serviceRoot.resolveFile(Strings.LIB_DL);
- List<FileObject> dljarFiles = Utils.findChildrenWithSuffix(libDlDir,
- Strings.DOT_JAR);
- for (FileObject jarFile : dljarFiles) {
- codebaseContext.addFile(jarFile);
+ /* Don't bother if there is no lib-dl (e.g. for simple clients) */
+ if (libDlDir.exists()) {
+ List<FileObject> dljarFiles = Utils.findChildrenWithSuffix(libDlDir,
+ Strings.DOT_JAR);
+ for (FileObject jarFile : dljarFiles) {
+ codebaseContext.addFile(jarFile);
+ }
}
}
@@ -209,10 +207,9 @@
Launch the service.
*/
log.log(Level.FINE, MessageNames.CALLING_MAIN, new Object[]{
- startClassName, Utils.format(args)
- });
+ startClassName, Utils.format(args)
+ });
Runnable task = new Runnable() {
-
@Override
public void run() {
try {
@@ -235,7 +232,7 @@
throw new LocalizedRuntimeException(MessageNames.BUNDLE_NAME,
MessageNames.CANT_READ_START_PROPERTIES,
new Object[]{Strings.START_PROPERTIES,
- serviceRoot.getName().getBaseName()});
+ serviceRoot.getName().getBaseName()});
}
Properties startProps = propertiesFileReader.getProperties(startProperties);
return startProps;
@@ -245,7 +242,7 @@
/*
Setup the liaison configuration.
*/
- ClassLoader originalContextCl=Thread.currentThread().getContextClassLoader();
+ ClassLoader originalContextCl = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(cl);
File workingDir = null;
@@ -299,8 +296,8 @@
FileObject configFile = fileUtility.getProfileDirectory().resolveFile(config);
InputStream in = configFile.getContent().getInputStream();
configNode = DeployerConfigParser.parseConfig(in);
- log.log(Level.FINE,MessageNames.STARTER_SERVICE_DEPLOYER_INITIALIZED,
- new Object[] {myName} );
+ log.log(Level.FINE, MessageNames.STARTER_SERVICE_DEPLOYER_INITIALIZED,
+ new Object[]{myName});
}
public ServiceLifeCycle deployServiceArchive(FileObject serviceArchive) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException {
@@ -311,7 +308,7 @@
serviceArchive.getFileSystem().getFileSystemManager().createFileSystem(Strings.JAR, serviceArchive));
String serviceName = findServiceName(env.getServiceArchive(), env.getServiceRoot());
env.setServiceName(serviceName);
- ServiceLifeCycle slc=StarterServiceLifeCycleSM.newStarterServiceLifeCycle(env, this);
+ ServiceLifeCycle slc = StarterServiceLifeCycleSM.newStarterServiceLifeCycle(env, this);
return slc;
}
@@ -330,7 +327,7 @@
}
void prepareService(ApplicationEnvironment env) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException {
-
+
CodeSource serviceCodeSource =
new CodeSource(findServiceURL(env.getServiceArchive(), env.getServiceRoot()),
new Certificate[0]);
@@ -338,7 +335,7 @@
new Object[]{env.getServiceName(), serviceCodeSource});
VirtualFileSystemClassLoader cl = createServiceClassloader(env.getServiceRoot(), serviceCodeSource);
env.setClassLoader(cl);
-
+
/*
Create a codebase context.
*/
@@ -359,13 +356,13 @@
Permission[] perms = createPermissionsInClassloader(cl);
grantPermissions(cl, perms);
setupLiaisonConfiguration(env.getServiceArchive(), env.getServiceRoot(), cl);
-
+
/*
* Create a working context (work manager).
*/
env.setWorkingContext(contextualWorkManager.createContext(env.getServiceName()));
}
-
+
void launchService(ApplicationEnvironment env, String[] serviceArgs) throws FileSystemException, IOException {
Properties startProps = readStartProperties(env.getServiceRoot());
String argLine = startProps.getProperty(Strings.START_PARAMETERS);
@@ -451,27 +448,27 @@
constructor.setAccessible(true);
return constructor.newInstance(parms, null);
}
-
+
/**
- * Attempt to stop the service in an orderly fashion.
- * Go to the service, see if it implements Administrable, then get the
- * admin proxy and see if it implements DestroyAdmin. If so, call it.
- * @param env
+ * Attempt to stop the service in an orderly fashion. Go to the service, see
+ * if it implements Administrable, then get the admin proxy and see if it
+ * implements DestroyAdmin. If so, call it.
+ *
+ * @param env
*/
- public void stopService(ApplicationEnvironment env) {
+ public void stopService(ApplicationEnvironment env) {
/* Option 1 - Service has a getAdmin() method - it probably implements
* Administrable.
*/
- Object serviceInstance=env.getServiceInstance();
- Method getAdmin=null;
+ Object serviceInstance = env.getServiceInstance();
+ Method getAdmin = null;
try {
- getAdmin=serviceInstance.getClass().getMethod(Strings.GET_ADMIN, new Class[0]);
+ getAdmin = serviceInstance.getClass().getMethod(Strings.GET_ADMIN, new Class[0]);
} catch (Exception ex) {
// Silent catch - leave it null;
}
if (getAdmin != null) {
-
}
-
+
}
}