diff options
author | duke <none@none> | 2007-12-01 00:00:00 +0000 |
---|---|---|
committer | duke <none@none> | 2007-12-01 00:00:00 +0000 |
commit | 59308f67f9b7038cfa2ceb9ee9ba27645b927cb5 (patch) | |
tree | 182810ab2fece13f57a928d026f93e9ede0827f9 /src/share/classes/sun/rmi |
Initial loadjdk7-b24
Diffstat (limited to 'src/share/classes/sun/rmi')
106 files changed, 28997 insertions, 0 deletions
diff --git a/src/share/classes/sun/rmi/log/LogHandler.java b/src/share/classes/sun/rmi/log/LogHandler.java new file mode 100644 index 000000000..0c7ede040 --- /dev/null +++ b/src/share/classes/sun/rmi/log/LogHandler.java @@ -0,0 +1,140 @@ +/* + * Copyright 1997-2001 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.log; + +import java.io.*; +import sun.rmi.server.MarshalOutputStream; +import sun.rmi.server.MarshalInputStream; + +/** + * A LogHandler represents snapshots and update records as serializable + * objects. + * + * This implementation does not know how to create an initial snaphot or + * apply an update to a snapshot. The client must specifiy these methods + * via a subclass. + * + * @see ReliableLog + * + * @author Ann Wollrath + */ +public abstract +class LogHandler { + + /** + * Creates a LogHandler for a ReliableLog. + */ + public LogHandler() {} + + /** + * Creates and returns the initial state of data structure that needs + * to be stably stored. This method is called when a ReliableLog is + * created. + * @return the initial state + * @exception Exception can raise any exception + */ + public abstract + Object initialSnapshot() throws Exception; + + /** + * Writes the snapshot object to a stream. This callback is + * invoked when the client calls the snaphot method of ReliableLog. + * @param out the output stream + * @param value the snapshot + * @exception Exception can raise any exception + */ + public + void snapshot(OutputStream out, Object value) throws Exception { + MarshalOutputStream s = new MarshalOutputStream(out); + s.writeObject(value); + s.flush(); + } + + /** + * Read the snapshot object from a stream and returns the snapshot. + * This callback is invoked when the client calls the recover method + * of ReliableLog. + * @param in the input stream + * @return the state (snapshot) + * @exception Exception can raise any exception + */ + + public + Object recover(InputStream in) throws Exception { + MarshalInputStream s = new MarshalInputStream(in); + return s.readObject(); + } + + /** + * Writes the representation (a serializable object) of an update + * to a stream. This callback is invoked when the client calls the + * update method of ReliableLog. + * @param out the output stream + * @param value the snapshot + * @exception Exception can raise any exception + */ + public + void writeUpdate(LogOutputStream out, Object value) throws Exception { + + MarshalOutputStream s = new MarshalOutputStream(out); + s.writeObject(value); + s.flush(); + } + + /** + * Reads a stably logged update (a serializable object) from a + * stream. This callback is invoked during recovery, once for + * every record in the log. After reading the update, this method + * invokes the applyUpdate (abstract) method in order to obtain + * the new snapshot value. It then returns the new snapshot. + * + * @param in the input stream + * @param state the current state + * @return the new state + * @exception Exception can raise any exception + */ + public + Object readUpdate(LogInputStream in, Object state) throws Exception { + MarshalInputStream s = new MarshalInputStream(in); + return applyUpdate(s.readObject(), state); + } + + /** + * Reads a stably logged update (a serializable object) from a stream. + * This callback is invoked during recovery, once for every record in the + * log. After reading the update, this method is invoked in order to + * obtain the new snapshot value. The method should apply the update + * object to the current state <code>state</code> and return the new + * state (the new snapshot value). + * @param update the update object + * @param state the current state + * @return the new state + * @exception Exception can raise any exception + */ + public abstract + Object applyUpdate(Object update, Object state) throws Exception; + +} diff --git a/src/share/classes/sun/rmi/log/LogInputStream.java b/src/share/classes/sun/rmi/log/LogInputStream.java new file mode 100644 index 000000000..cdf6e2a1d --- /dev/null +++ b/src/share/classes/sun/rmi/log/LogInputStream.java @@ -0,0 +1,134 @@ +/* + * Copyright 1997-1999 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.log; + +import java.io.*; + +public +class LogInputStream extends InputStream { + private InputStream in; + private int length; + + /** + * Creates a log input file with the specified system dependent + * file descriptor. + * @param fd the system dependent file descriptor + * @param length the total number of bytes allowed to be read + * @exception IOException If an I/O error has occurred. + */ + public LogInputStream(InputStream in, int length) throws IOException { + this.in = in; + this.length = length; + } + + /** + * Reads a byte of data. This method will block if no input is + * available. + * @return the byte read, or -1 if the end of the log or end of the + * stream is reached. + * @exception IOException If an I/O error has occurred. + */ + public int read() throws IOException { + if (length == 0) + return -1; + int c = in.read(); + length = (c != -1) ? length - 1 : 0; + return c; + } + + /** + * Reads data into an array of bytes. + * This method blocks until some input is available. + * @param b the buffer into which the data is read + * @return the actual number of bytes read, or -1 if the end of the log + * or end of the stream is reached. + * @exception IOException If an I/O error has occurred. + */ + public int read(byte b[]) throws IOException { + return read(b, 0, b.length); + } + + /** + * Reads data into an array of bytes. + * This method blocks until some input is available. + * @param b the buffer into which the data is read + * @param off the start offset of the data + * @param len the maximum number of bytes read + * @return the actual number of bytes read, or -1 if the end of the log or + * end of the stream is reached. + * @exception IOException If an I/O error has occurred. + */ + public int read(byte b[], int off, int len) throws IOException { + if (length == 0) + return -1; + len = (length < len) ? length : len; + int n = in.read(b, off, len); + length = (n != -1) ? length - n : 0; + return n; + } + + /** + * Skips n bytes of input. + * @param n the number of bytes to be skipped + * @return the actual number of bytes skipped. + * @exception IOException If an I/O error has occurred. + */ + public long skip(long n) throws IOException { + if (n > Integer.MAX_VALUE) + throw new IOException("Too many bytes to skip - " + n); + if (length == 0) + return 0; + n = (length < n) ? length : n; + n = in.skip(n); + length -= n; + return n; + } + + /** + * Returns the number of bytes that can be read without blocking. + * @return the number of available bytes, which is initially + * equal to the file size. + */ + public int available() throws IOException { + int avail = in.available(); + return (length < avail) ? length : avail; + } + + /** + * Closes the input stream. No further input can be read. + * the stream. + */ + public void close() { + length = 0; + } + + /** + * Closes the stream when garbage is collected. + */ + protected void finalize() throws IOException { + close(); + } +} diff --git a/src/share/classes/sun/rmi/log/LogOutputStream.java b/src/share/classes/sun/rmi/log/LogOutputStream.java new file mode 100644 index 000000000..b706aecf0 --- /dev/null +++ b/src/share/classes/sun/rmi/log/LogOutputStream.java @@ -0,0 +1,83 @@ +/* + * Copyright 1997 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.log; + +import java.io.*; + +public +class LogOutputStream extends OutputStream { + + private RandomAccessFile raf; + + /** + * Creates an output file with the specified system dependent + * file descriptor. + * @param fd the system dependent file descriptor + * @exception IOException If an I/O error has occurred. + */ + public LogOutputStream(RandomAccessFile raf) throws IOException { + this.raf = raf; + } + + /** + * Writes a byte of data. This method will block until the byte is + * actually written. + * @param b the byte to be written + * @exception IOException If an I/O error has occurred. + */ + public void write(int b) throws IOException { + raf.write(b); + } + + /** + * Writes an array of bytes. Will block until the bytes + * are actually written. + * @param b the data to be written + * @exception IOException If an I/O error has occurred. + */ + public void write(byte b[]) throws IOException { + raf.write(b); + } + + /** + * Writes a sub array of bytes. + * @param b the data to be written + * @param off the start offset in the data + * @param len the number of bytes that are written + * @exception IOException If an I/O error has occurred. + */ + public void write(byte b[], int off, int len) throws IOException { + raf.write(b, off, len); + } + + /** + * Can not close a LogOutputStream, so this does nothing. + * @exception IOException If an I/O error has occurred. + */ + public final void close() throws IOException { + } + +} diff --git a/src/share/classes/sun/rmi/log/ReliableLog.java b/src/share/classes/sun/rmi/log/ReliableLog.java new file mode 100644 index 000000000..7ac285819 --- /dev/null +++ b/src/share/classes/sun/rmi/log/ReliableLog.java @@ -0,0 +1,829 @@ +/* + * Copyright 1997-2005 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.log; + +import java.io.*; +import java.lang.reflect.Constructor; +import java.rmi.server.RMIClassLoader; +import java.security.AccessController; +import java.security.PrivilegedAction; +import sun.security.action.GetBooleanAction; +import sun.security.action.GetPropertyAction; + +/** + * This class is a simple implementation of a reliable Log. The + * client of a ReliableLog must provide a set of callbacks (via a + * LogHandler) that enables a ReliableLog to read and write + * checkpoints and log records. This implementation ensures that the + * current value of the data stored (via a ReliableLog) is recoverable + * after a system crash. <p> + * + * The secondary storage strategy is to record values in files using a + * representation of the caller's choosing. Two sorts of files are + * kept: snapshots and logs. At any instant, one snapshot is current. + * The log consists of a sequence of updates that have occurred since + * the current snapshot was taken. The current stable state is the + * value of the snapshot, as modified by the sequence of updates in + * the log. From time to time, the client of a ReliableLog instructs + * the package to make a new snapshot and clear the log. A ReliableLog + * arranges disk writes such that updates are stable (as long as the + * changes are force-written to disk) and atomic : no update is lost, + * and each update either is recorded completely in the log or not at + * all. Making a new snapshot is also atomic. <p> + * + * Normal use for maintaining the recoverable store is as follows: The + * client maintains the relevant data structure in virtual memory. As + * updates happen to the structure, the client informs the ReliableLog + * (all it "log") by calling log.update. Periodically, the client + * calls log.snapshot to provide the current value of the data + * structure. On restart, the client calls log.recover to obtain the + * latest snapshot and the following sequences of updates; the client + * applies the updates to the snapshot to obtain the state that + * existed before the crash. <p> + * + * The current logfile format is: <ol> + * <li> a format version number (two 4-octet integers, major and + * minor), followed by + * <li> a sequence of log records. Each log record contains, in + * order, <ol> + * <li> a 4-octet integer representing the length of the following log + * data, + * <li> the log data (variable length). </ol> </ol> <p> + * + * @see LogHandler + * + * @author Ann Wollrath + * + */ +public class ReliableLog { + + public final static int PreferredMajorVersion = 0; + public final static int PreferredMinorVersion = 2; + + // sun.rmi.log.debug=false + private boolean Debug = false; + + private static String snapshotPrefix = "Snapshot."; + private static String logfilePrefix = "Logfile."; + private static String versionFile = "Version_Number"; + private static String newVersionFile = "New_Version_Number"; + private static int intBytes = 4; + private static long diskPageSize = 512; + + private File dir; // base directory + private int version = 0; // current snapshot and log version + private String logName = null; + private LogFile log = null; + private long snapshotBytes = 0; + private long logBytes = 0; + private int logEntries = 0; + private long lastSnapshot = 0; + private long lastLog = 0; + //private long padBoundary = intBytes; + private LogHandler handler; + private final byte[] intBuf = new byte[4]; + + // format version numbers read from/written to this.log + private int majorFormatVersion = 0; + private int minorFormatVersion = 0; + + + /** + * Constructor for the log file. If the system property + * sun.rmi.log.class is non-null and the class specified by this + * property a) can be loaded, b) is a subclass of LogFile, and c) has a + * public two-arg constructor (String, String), ReliableLog uses the + * constructor to construct the LogFile. + **/ + private static final Constructor<? extends LogFile> + logClassConstructor = getLogClassConstructor(); + + /** + * Creates a ReliableLog to handle checkpoints and logging in a + * stable storage directory. + * + * @param dirPath path to the stable storage directory + * @param logCl the closure object containing callbacks for logging and + * recovery + * @param pad ignored + * @exception IOException If a directory creation error has + * occurred or if initialSnapshot callback raises an exception or + * if an exception occurs during invocation of the handler's + * snapshot method or if other IOException occurs. + */ + public ReliableLog(String dirPath, + LogHandler handler, + boolean pad) + throws IOException + { + super(); + this.Debug = ((Boolean) AccessController.doPrivileged( + new GetBooleanAction("sun.rmi.log.debug"))).booleanValue(); + dir = new File(dirPath); + if (!(dir.exists() && dir.isDirectory())) { + // create directory + if (!dir.mkdir()) { + throw new IOException("could not create directory for log: " + + dirPath); + } + } + //padBoundary = (pad ? diskPageSize : intBytes); + this.handler = handler; + lastSnapshot = 0; + lastLog = 0; + getVersion(); + if (version == 0) { + try { + snapshot(handler.initialSnapshot()); + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw new IOException("initial snapshot failed with " + + "exception: " + e); + } + } + } + + /** + * Creates a ReliableLog to handle checkpoints and logging in a + * stable storage directory. + * + * @param dirPath path to the stable storage directory + * @param logCl the closure object containing callbacks for logging and + * recovery + * @exception IOException If a directory creation error has + * occurred or if initialSnapshot callback raises an exception + */ + public ReliableLog(String dirPath, + LogHandler handler) + throws IOException + { + this(dirPath, handler, false); + } + + /* public methods */ + + /** + * Returns an object which is the value recorded in the current + * snapshot. This snapshot is recovered by calling the client + * supplied callback "recover" and then subsequently invoking + * the "readUpdate" callback to apply any logged updates to the state. + * + * @exception IOException If recovery fails due to serious log + * corruption, read update failure, or if an exception occurs + * during the recover callback + */ + public synchronized Object recover() + throws IOException + { + if (Debug) + System.err.println("log.debug: recover()"); + + if (version == 0) + return null; + + Object snapshot; + String fname = versionName(snapshotPrefix); + File snapshotFile = new File(fname); + InputStream in = + new BufferedInputStream(new FileInputStream(snapshotFile)); + + if (Debug) + System.err.println("log.debug: recovering from " + fname); + + try { + try { + snapshot = handler.recover(in); + + } catch (IOException e) { + throw e; + } catch (Exception e) { + if (Debug) + System.err.println("log.debug: recovery failed: " + e); + throw new IOException("log recover failed with " + + "exception: " + e); + } + snapshotBytes = snapshotFile.length(); + } finally { + in.close(); + } + + return recoverUpdates(snapshot); + } + + /** + * Records this update in the log file (does not force update to disk). + * The update is recorded by calling the client's "writeUpdate" callback. + * This method must not be called until this log's recover method has + * been invoked (and completed). + * + * @param value the object representing the update + * @exception IOException If an exception occurred during a + * writeUpdate callback or if other I/O error has occurred. + */ + public synchronized void update(Object value) throws IOException { + update(value, true); + } + + /** + * Records this update in the log file. The update is recorded by + * calling the client's writeUpdate callback. This method must not be + * called until this log's recover method has been invoked + * (and completed). + * + * @param value the object representing the update + * @param forceToDisk ignored; changes are always forced to disk + * @exception IOException If force-write to log failed or an + * exception occurred during the writeUpdate callback or if other + * I/O error occurs while updating the log. + */ + public synchronized void update(Object value, boolean forceToDisk) + throws IOException + { + // avoid accessing a null log field. + if (log == null) { + throw new IOException("log is inaccessible, " + + "it may have been corrupted or closed"); + } + + /* + * If the entry length field spans a sector boundary, write + * the high order bit of the entry length, otherwise write zero for + * the entry length. + */ + long entryStart = log.getFilePointer(); + boolean spansBoundary = log.checkSpansBoundary(entryStart); + writeInt(log, spansBoundary? 1<<31 : 0); + + /* + * Write update, and sync. + */ + try { + handler.writeUpdate(new LogOutputStream(log), value); + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw (IOException) + new IOException("write update failed").initCause(e); + } + log.sync(); + + long entryEnd = log.getFilePointer(); + int updateLen = (int) ((entryEnd - entryStart) - intBytes); + log.seek(entryStart); + + if (spansBoundary) { + /* + * If length field spans a sector boundary, then + * the next two steps are required (see 4652922): + * + * 1) Write actual length with high order bit set; sync. + * 2) Then clear high order bit of length; sync. + */ + writeInt(log, updateLen | 1<<31); + log.sync(); + + log.seek(entryStart); + log.writeByte(updateLen >> 24); + log.sync(); + + } else { + /* + * Write actual length; sync. + */ + writeInt(log, updateLen); + log.sync(); + } + + log.seek(entryEnd); + logBytes = entryEnd; + lastLog = System.currentTimeMillis(); + logEntries++; + } + + /** + * Returns the constructor for the log file if the system property + * sun.rmi.log.class is non-null and the class specified by the + * property a) can be loaded, b) is a subclass of LogFile, and c) has a + * public two-arg constructor (String, String); otherwise returns null. + **/ + private static Constructor<? extends LogFile> + getLogClassConstructor() { + + String logClassName = ((String) AccessController.doPrivileged( + new GetPropertyAction("sun.rmi.log.class"))); + if (logClassName != null) { + try { + ClassLoader loader = + AccessController.doPrivileged( + new PrivilegedAction<ClassLoader>() { + public ClassLoader run() { + return ClassLoader.getSystemClassLoader(); + } + }); + Class cl = loader.loadClass(logClassName); + if (LogFile.class.isAssignableFrom(cl)) { + return cl.getConstructor(String.class, String.class); + } + } catch (Exception e) { + System.err.println("Exception occurred:"); + e.printStackTrace(); + } + } + return null; + } + + /** + * Records this value as the current snapshot by invoking the client + * supplied "snapshot" callback and then empties the log. + * + * @param value the object representing the new snapshot + * @exception IOException If an exception occurred during the + * snapshot callback or if other I/O error has occurred during the + * snapshot process + */ + public synchronized void snapshot(Object value) + throws IOException + { + int oldVersion = version; + incrVersion(); + + String fname = versionName(snapshotPrefix); + File snapshotFile = new File(fname); + FileOutputStream out = new FileOutputStream(snapshotFile); + try { + try { + handler.snapshot(out, value); + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw new IOException("snapshot failed with exception of type: " + + e.getClass().getName() + + ", message was: " + e.getMessage()); + } + lastSnapshot = System.currentTimeMillis(); + } finally { + out.close(); + snapshotBytes = snapshotFile.length(); + } + + openLogFile(true); + writeVersionFile(true); + commitToNewVersion(); + deleteSnapshot(oldVersion); + deleteLogFile(oldVersion); + } + + /** + * Close the stable storage directory in an orderly manner. + * + * @exception IOException If an I/O error occurs when the log is + * closed + */ + public synchronized void close() throws IOException { + if (log == null) return; + try { + log.close(); + } finally { + log = null; + } + } + + /** + * Returns the size of the snapshot file in bytes; + */ + public long snapshotSize() { + return snapshotBytes; + } + + /** + * Returns the size of the log file in bytes; + */ + public long logSize() { + return logBytes; + } + + /* private methods */ + + /** + * Write an int value in single write operation. This method + * assumes that the caller is synchronized on the log file. + * + * @param out output stream + * @param val int value + * @throws IOException if any other I/O error occurs + */ + private void writeInt(DataOutput out, int val) + throws IOException + { + intBuf[0] = (byte) (val >> 24); + intBuf[1] = (byte) (val >> 16); + intBuf[2] = (byte) (val >> 8); + intBuf[3] = (byte) val; + out.write(intBuf); + } + + /** + * Generates a filename prepended with the stable storage directory path. + * + * @param name the leaf name of the file + */ + private String fName(String name) { + return dir.getPath() + File.separator + name; + } + + /** + * Generates a version 0 filename prepended with the stable storage + * directory path + * + * @param name version file name + */ + private String versionName(String name) { + return versionName(name, 0); + } + + /** + * Generates a version filename prepended with the stable storage + * directory path with the version number as a suffix. + * + * @param name version file name + * @thisversion a version number + */ + private String versionName(String prefix, int ver) { + ver = (ver == 0) ? version : ver; + return fName(prefix) + String.valueOf(ver); + } + + /** + * Increments the directory version number. + */ + private void incrVersion() { + do { version++; } while (version==0); + } + + /** + * Delete a file. + * + * @param name the name of the file + * @exception IOException If new version file couldn't be removed + */ + private void deleteFile(String name) throws IOException { + + File f = new File(name); + if (!f.delete()) + throw new IOException("couldn't remove file: " + name); + } + + /** + * Removes the new version number file. + * + * @exception IOException If an I/O error has occurred. + */ + private void deleteNewVersionFile() throws IOException { + deleteFile(fName(newVersionFile)); + } + + /** + * Removes the snapshot file. + * + * @param ver the version to remove + * @exception IOException If an I/O error has occurred. + */ + private void deleteSnapshot(int ver) throws IOException { + if (ver == 0) return; + deleteFile(versionName(snapshotPrefix, ver)); + } + + /** + * Removes the log file. + * + * @param ver the version to remove + * @exception IOException If an I/O error has occurred. + */ + private void deleteLogFile(int ver) throws IOException { + if (ver == 0) return; + deleteFile(versionName(logfilePrefix, ver)); + } + + /** + * Opens the log file in read/write mode. If file does not exist, it is + * created. + * + * @param truncate if true and file exists, file is truncated to zero + * length + * @exception IOException If an I/O error has occurred. + */ + private void openLogFile(boolean truncate) throws IOException { + try { + close(); + } catch (IOException e) { /* assume this is okay */ + } + + logName = versionName(logfilePrefix); + + try { + log = (logClassConstructor == null ? + new LogFile(logName, "rw") : + logClassConstructor.newInstance(logName, "rw")); + } catch (Exception e) { + throw (IOException) new IOException( + "unable to construct LogFile instance").initCause(e); + } + + if (truncate) { + initializeLogFile(); + } + } + + /** + * Creates a new log file, truncated and initialized with the format + * version number preferred by this implementation. + * <p>Environment: inited, synchronized + * <p>Precondition: valid: log, log contains nothing useful + * <p>Postcondition: if successful, log is initialised with the format + * version number (Preferred{Major,Minor}Version), and logBytes is + * set to the resulting size of the updatelog, and logEntries is set to + * zero. Otherwise, log is in an indeterminate state, and logBytes + * is unchanged, and logEntries is unchanged. + * + * @exception IOException If an I/O error has occurred. + */ + private void initializeLogFile() + throws IOException + { + log.setLength(0); + majorFormatVersion = PreferredMajorVersion; + writeInt(log, PreferredMajorVersion); + minorFormatVersion = PreferredMinorVersion; + writeInt(log, PreferredMinorVersion); + logBytes = intBytes * 2; + logEntries = 0; + } + + + /** + * Writes out version number to file. + * + * @param newVersion if true, writes to a new version file + * @exception IOException If an I/O error has occurred. + */ + private void writeVersionFile(boolean newVersion) throws IOException { + String name; + if (newVersion) { + name = newVersionFile; + } else { + name = versionFile; + } + DataOutputStream out = + new DataOutputStream(new FileOutputStream(fName(name))); + writeInt(out, version); + out.close(); + } + + /** + * Creates the initial version file + * + * @exception IOException If an I/O error has occurred. + */ + private void createFirstVersion() throws IOException { + version = 0; + writeVersionFile(false); + } + + /** + * Commits (atomically) the new version. + * + * @exception IOException If an I/O error has occurred. + */ + private void commitToNewVersion() throws IOException { + writeVersionFile(false); + deleteNewVersionFile(); + } + + /** + * Reads version number from a file. + * + * @param name the name of the version file + * @return the version + * @exception IOException If an I/O error has occurred. + */ + private int readVersion(String name) throws IOException { + DataInputStream in = new DataInputStream(new FileInputStream(name)); + try { + return in.readInt(); + } finally { + in.close(); + } + } + + /** + * Sets the version. If version file does not exist, the initial + * version file is created. + * + * @exception IOException If an I/O error has occurred. + */ + private void getVersion() throws IOException { + try { + version = readVersion(fName(newVersionFile)); + commitToNewVersion(); + } catch (IOException e) { + try { + deleteNewVersionFile(); + } + catch (IOException ex) { + } + + try { + version = readVersion(fName(versionFile)); + } + catch (IOException ex) { + createFirstVersion(); + } + } + } + + /** + * Applies outstanding updates to the snapshot. + * + * @param state the most recent snapshot + * @exception IOException If serious log corruption is detected or + * if an exception occurred during a readUpdate callback or if + * other I/O error has occurred. + * @return the resulting state of the object after all updates + */ + private Object recoverUpdates(Object state) + throws IOException + { + logBytes = 0; + logEntries = 0; + + if (version == 0) return state; + + String fname = versionName(logfilePrefix); + InputStream in = + new BufferedInputStream(new FileInputStream(fname)); + DataInputStream dataIn = new DataInputStream(in); + + if (Debug) + System.err.println("log.debug: reading updates from " + fname); + + try { + majorFormatVersion = dataIn.readInt(); logBytes += intBytes; + minorFormatVersion = dataIn.readInt(); logBytes += intBytes; + } catch (EOFException e) { + /* This is a log which was corrupted and/or cleared (by + * fsck or equivalent). This is not an error. + */ + openLogFile(true); // create and truncate + in = null; + } + /* A new major version number is a catastrophe (it means + * that the file format is incompatible with older + * clients, and we'll only be breaking things by trying to + * use the log). A new minor version is no big deal for + * upward compatibility. + */ + if (majorFormatVersion != PreferredMajorVersion) { + if (Debug) { + System.err.println("log.debug: major version mismatch: " + + majorFormatVersion + "." + minorFormatVersion); + } + throw new IOException("Log file " + logName + " has a " + + "version " + majorFormatVersion + + "." + minorFormatVersion + + " format, and this implementation " + + " understands only version " + + PreferredMajorVersion + "." + + PreferredMinorVersion); + } + + try { + while (in != null) { + int updateLen = 0; + + try { + updateLen = dataIn.readInt(); + } catch (EOFException e) { + if (Debug) + System.err.println("log.debug: log was sync'd cleanly"); + break; + } + if (updateLen <= 0) {/* crashed while writing last log entry */ + if (Debug) { + System.err.println( + "log.debug: last update incomplete, " + + "updateLen = 0x" + + Integer.toHexString(updateLen)); + } + break; + } + + // this is a fragile use of available() which relies on the + // twin facts that BufferedInputStream correctly consults + // the underlying stream, and that FileInputStream returns + // the number of bytes remaining in the file (via FIONREAD). + if (in.available() < updateLen) { + /* corrupted record at end of log (can happen since we + * do only one fsync) + */ + if (Debug) + System.err.println("log.debug: log was truncated"); + break; + } + + if (Debug) + System.err.println("log.debug: rdUpdate size " + updateLen); + try { + state = handler.readUpdate(new LogInputStream(in, updateLen), + state); + } catch (IOException e) { + throw e; + } catch (Exception e) { + e.printStackTrace(); + throw new IOException("read update failed with " + + "exception: " + e); + } + logBytes += (intBytes + updateLen); + logEntries++; + } /* while */ + } finally { + if (in != null) + in.close(); + } + + if (Debug) + System.err.println("log.debug: recovered updates: " + logEntries); + + /* reopen log file at end */ + openLogFile(false); + + // avoid accessing a null log field + if (log == null) { + throw new IOException("rmid's log is inaccessible, " + + "it may have been corrupted or closed"); + } + + log.seek(logBytes); + log.setLength(logBytes); + + return state; + } + + /** + * ReliableLog's log file implementation. This implementation + * is subclassable for testing purposes. + */ + public static class LogFile extends RandomAccessFile { + + private final FileDescriptor fd; + + /** + * Constructs a LogFile and initializes the file descriptor. + **/ + public LogFile(String name, String mode) + throws FileNotFoundException, IOException + { + super(name, mode); + this.fd = getFD(); + } + + /** + * Invokes sync on the file descriptor for this log file. + */ + protected void sync() throws IOException { + fd.sync(); + } + + /** + * Returns true if writing 4 bytes starting at the specified file + * position, would span a 512 byte sector boundary; otherwise returns + * false. + **/ + protected boolean checkSpansBoundary(long fp) { + return fp % 512 > 508; + } + } +} diff --git a/src/share/classes/sun/rmi/registry/RegistryImpl.java b/src/share/classes/sun/rmi/registry/RegistryImpl.java new file mode 100644 index 000000000..2f29cd570 --- /dev/null +++ b/src/share/classes/sun/rmi/registry/RegistryImpl.java @@ -0,0 +1,360 @@ +/* + * Copyright 1996-2006 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.registry; + +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.io.IOException; +import java.net.*; +import java.rmi.*; +import java.rmi.server.ObjID; +import java.rmi.server.RemoteServer; +import java.rmi.server.ServerNotActiveException; +import java.rmi.registry.Registry; +import java.rmi.server.RMIClientSocketFactory; +import java.rmi.server.RMIServerSocketFactory; +import java.security.PrivilegedActionException; +import java.text.MessageFormat; +import sun.rmi.server.UnicastServerRef; +import sun.rmi.server.UnicastServerRef2; +import sun.rmi.transport.LiveRef; +import sun.rmi.transport.ObjectTable; +import sun.rmi.transport.Target; + +/** + * A "registry" exists on every node that allows RMI connections to + * servers on that node. The registry on a particular node contains a + * transient database that maps names to remote objects. When the + * node boots, the registry database is empty. The names stored in the + * registry are pure and are not parsed. A service storing itself in + * the registry may want to prefix its name of the service by a package + * name (although not required), to reduce name collisions in the + * registry. + * + * The LocateRegistry class is used to obtain registry for different hosts. + * + * @see java.rmi.registry.LocateRegistry + */ +public class RegistryImpl extends java.rmi.server.RemoteServer + implements Registry +{ + + /* indicate compatibility with JDK 1.1.x version of class */ + private static final long serialVersionUID = 4666870661827494597L; + private Hashtable bindings = new Hashtable(101); + private static Hashtable allowedAccessCache = new Hashtable(3); + private static RegistryImpl registry; + private static ObjID id = new ObjID(ObjID.REGISTRY_ID); + + private static ResourceBundle resources = null; + + /** + * Construct a new RegistryImpl on the specified port with the + * given custom socket factory pair. + */ + public RegistryImpl(int port, + RMIClientSocketFactory csf, + RMIServerSocketFactory ssf) + throws RemoteException + { + LiveRef lref = new LiveRef(id, port, csf, ssf); + setup(new UnicastServerRef2(lref)); + } + + /** + * Construct a new RegistryImpl on the specified port. + */ + public RegistryImpl(int port) + throws RemoteException + { + LiveRef lref = new LiveRef(id, port); + setup(new UnicastServerRef(lref)); + } + + /* + * Create the export the object using the parameter + * <code>uref</code> + */ + private void setup(UnicastServerRef uref) + throws RemoteException + { + /* Server ref must be created and assigned before remote + * object 'this' can be exported. + */ + ref = uref; + uref.exportObject(this, null, true); + } + + /** + * Returns the remote object for specified name in the registry. + * @exception RemoteException If remote operation failed. + * @exception NotBound If name is not currently bound. + */ + public Remote lookup(String name) + throws RemoteException, NotBoundException + { + synchronized (bindings) { + Remote obj = (Remote)bindings.get(name); + if (obj == null) + throw new NotBoundException(name); + return obj; + } + } + + /** + * Binds the name to the specified remote object. + * @exception RemoteException If remote operation failed. + * @exception AlreadyBoundException If name is already bound. + */ + public void bind(String name, Remote obj) + throws RemoteException, AlreadyBoundException, AccessException + { + checkAccess("Registry.bind"); + synchronized (bindings) { + Remote curr = (Remote)bindings.get(name); + if (curr != null) + throw new AlreadyBoundException(name); + bindings.put(name, obj); + } + } + + /** + * Unbind the name. + * @exception RemoteException If remote operation failed. + * @exception NotBound If name is not currently bound. + */ + public void unbind(String name) + throws RemoteException, NotBoundException, AccessException + { + checkAccess("Registry.unbind"); + synchronized (bindings) { + Remote obj = (Remote)bindings.get(name); + if (obj == null) + throw new NotBoundException(name); + bindings.remove(name); + } + } + + /** + * Rebind the name to a new object, replaces any existing binding. + * @exception RemoteException If remote operation failed. + */ + public void rebind(String name, Remote obj) + throws RemoteException, AccessException + { + checkAccess("Registry.rebind"); + bindings.put(name, obj); + } + + /** + * Returns an enumeration of the names in the registry. + * @exception RemoteException If remote operation failed. + */ + public String[] list() + throws RemoteException + { + String[] names; + synchronized (bindings) { + int i = bindings.size(); + names = new String[i]; + Enumeration enum_ = bindings.keys(); + while ((--i) >= 0) + names[i] = (String)enum_.nextElement(); + } + return names; + } + + /** + * Check that the caller has access to perform indicated operation. + * The client must be on same the same host as this server. + */ + public static void checkAccess(String op) throws AccessException { + + try { + /* + * Get client host that this registry operation was made from. + */ + final String clientHostName = getClientHost(); + InetAddress clientHost; + + try { + clientHost = (InetAddress) + java.security.AccessController.doPrivileged( + new java.security.PrivilegedExceptionAction() { + public Object run() + throws java.net.UnknownHostException + { + return InetAddress.getByName(clientHostName); + } + }); + } catch (PrivilegedActionException pae) { + throw (java.net.UnknownHostException) pae.getException(); + } + + // if client not yet seen, make sure client allowed access + if (allowedAccessCache.get(clientHost) == null) { + + if (clientHost.isAnyLocalAddress()) { + throw new AccessException( + "Registry." + op + " disallowed; origin unknown"); + } + + try { + final InetAddress finalClientHost = clientHost; + + java.security.AccessController.doPrivileged( + new java.security.PrivilegedExceptionAction() { + public Object run() throws java.io.IOException { + /* + * if a ServerSocket can be bound to the client's + * address then that address must be local + */ + (new ServerSocket(0, 10, finalClientHost)).close(); + allowedAccessCache.put(finalClientHost, + finalClientHost); + return null; + } + }); + } catch (PrivilegedActionException pae) { + // must have been an IOException + + throw new AccessException( + "Registry." + op + " disallowed; origin " + + clientHost + " is non-local host"); + } + } + } catch (ServerNotActiveException ex) { + /* + * Local call from this VM: allow access. + */ + } catch (java.net.UnknownHostException ex) { + throw new AccessException("Registry." + op + + " disallowed; origin is unknown host"); + } + } + + public static ObjID getID() { + return id; + } + + /** + * Retrieves text resources from the locale-specific properties file. + */ + private static String getTextResource(String key) { + if (resources == null) { + try { + resources = ResourceBundle.getBundle( + "sun.rmi.registry.resources.rmiregistry"); + } catch (MissingResourceException mre) { + } + if (resources == null) { + // throwing an Error is a bit extreme, methinks + return ("[missing resource file: " + key + "]"); + } + } + + String val = null; + try { + val = resources.getString(key); + } catch (MissingResourceException mre) { + } + + if (val == null) { + return ("[missing resource: " + key + "]"); + } else { + return (val); + } + } + + /** + * Main program to start a registry. <br> + * The port number can be specified on the command line. + */ + public static void main(String args[]) + { + // Create and install the security manager if one is not installed + // already. + if (System.getSecurityManager() == null) { + System.setSecurityManager(new RMISecurityManager()); + } + + try { + /* + * Fix bugid 4147561: When JDK tools are executed, the value of + * the CLASSPATH environment variable for the shell in which they + * were invoked is no longer incorporated into the application + * class path; CLASSPATH's only effect is to be the value of the + * system property "env.class.path". To preserve the previous + * (JDK1.1 and JDK1.2beta3) behavior of this tool, however, its + * CLASSPATH should still be considered when resolving classes + * being unmarshalled. To effect this old behavior, a class + * loader that loads from the file path specified in the + * "env.class.path" property is created and set to be the context + * class loader before the remote object is exported. + */ + String envcp = System.getProperty("env.class.path"); + if (envcp == null) { + envcp = "."; // preserve old default behavior + } + URL[] urls = sun.misc.URLClassPath.pathToURLs(envcp); + ClassLoader cl = new URLClassLoader(urls); + + /* + * Fix bugid 4242317: Classes defined by this class loader should + * be annotated with the value of the "java.rmi.server.codebase" + * property, not the "file:" URLs for the CLASSPATH elements. + */ + sun.rmi.server.LoaderHandler.registerCodebaseLoader(cl); + + Thread.currentThread().setContextClassLoader(cl); + + int regPort = Registry.REGISTRY_PORT; + if (args.length >= 1) { + regPort = Integer.parseInt(args[0]); + } + registry = new RegistryImpl(regPort); + // prevent registry from exiting + while (true) { + try { + Thread.sleep(Long.MAX_VALUE); + } catch (InterruptedException e) { + } + } + } catch (NumberFormatException e) { + System.err.println(MessageFormat.format( + getTextResource("rmiregistry.port.badnumber"), + args[0] )); + System.err.println(MessageFormat.format( + getTextResource("rmiregistry.usage"), + "rmiregistry" )); + } catch (Exception e) { + e.printStackTrace(); + } + System.exit(1); + } +} diff --git a/src/share/classes/sun/rmi/registry/resources/rmiregistry.properties b/src/share/classes/sun/rmi/registry/resources/rmiregistry.properties new file mode 100644 index 000000000..49b789d47 --- /dev/null +++ b/src/share/classes/sun/rmi/registry/resources/rmiregistry.properties @@ -0,0 +1,31 @@ +# +# +# Copyright 1999-2001 Sun Microsystems, Inc. All Rights Reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Sun designates this +# particular file as subject to the "Classpath" exception as provided +# by Sun in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, +# CA 95054 USA or visit www.sun.com if you need additional information or +# have any questions. +# + +rmiregistry.usage=Usage: {0} <options> <port>\ +\n\ +\nwhere <options> includes:\ +\n -J<runtime flag> Pass argument to the java interpreter +rmiregistry.port.badnumber=port argument, {0}, is not a number. diff --git a/src/share/classes/sun/rmi/registry/resources/rmiregistry_de.properties b/src/share/classes/sun/rmi/registry/resources/rmiregistry_de.properties new file mode 100644 index 000000000..d01ab9850 --- /dev/null +++ b/src/share/classes/sun/rmi/registry/resources/rmiregistry_de.properties @@ -0,0 +1,31 @@ +# +# +# Copyright 2000-2005 Sun Microsystems, Inc. All Rights Reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Sun designates this +# particular file as subject to the "Classpath" exception as provided +# by Sun in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, +# CA 95054 USA or visit www.sun.com if you need additional information or +# have any questions. +# + +rmiregistry.usage=Syntax: {0} <Optionen> <Port>\ +\n\ +\nmit folgendem Wert f\u00fcr <Optionen>:\ +\n -J <Laufzeit-Flag> \u00dcbergeben des Arguments an den Java-Interpreter. +rmiregistry.port.badnumber=Anschlussargument {0} ist keine Zahl. diff --git a/src/share/classes/sun/rmi/registry/resources/rmiregistry_es.properties b/src/share/classes/sun/rmi/registry/resources/rmiregistry_es.properties new file mode 100644 index 000000000..1d9fc13a9 --- /dev/null +++ b/src/share/classes/sun/rmi/registry/resources/rmiregistry_es.properties @@ -0,0 +1,31 @@ +# +# +# Copyright 2000-2005 Sun Microsystems, Inc. All Rights Reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Sun designates this +# particular file as subject to the "Classpath" exception as provided +# by Sun in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, +# CA 95054 USA or visit www.sun.com if you need additional information or +# have any questions. +# + +rmiregistry.usage=Sintaxis: {0} <opciones> <puerto>\ +\n\ +\ndonde las <opciones> son:\ +\n -J<indicador de runtime> Pasar argumento al int\u00e9rprete de java +rmiregistry.port.badnumber=argumento de puerto, {0}, no es un n\u00famero. diff --git a/src/share/classes/sun/rmi/registry/resources/rmiregistry_fr.properties b/src/share/classes/sun/rmi/registry/resources/rmiregistry_fr.properties new file mode 100644 index 000000000..74ff3903a --- /dev/null +++ b/src/share/classes/sun/rmi/registry/resources/rmiregistry_fr.properties @@ -0,0 +1,31 @@ +# +# +# Copyright 2000-2005 Sun Microsystems, Inc. All Rights Reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Sun designates this +# particular file as subject to the "Classpath" exception as provided +# by Sun in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, +# CA 95054 USA or visit www.sun.com if you need additional information or +# have any questions. +# + +rmiregistry.usage=Syntaxe : {0} <options> <port>\ +\n\ +\no\u00f9 <options> comprend :\ +\n -J<indicateur d''ex\u00e9cution> Transmettre l''argument \u00e0 l''interpr\u00e9teur Java +rmiregistry.port.badnumber=l''argument de port, {0}, n''est pas un nombre. diff --git a/src/share/classes/sun/rmi/registry/resources/rmiregistry_it.properties b/src/share/classes/sun/rmi/registry/resources/rmiregistry_it.properties new file mode 100644 index 000000000..e53e4a6a8 --- /dev/null +++ b/src/share/classes/sun/rmi/registry/resources/rmiregistry_it.properties @@ -0,0 +1,31 @@ +# +# +# Copyright 2000-2005 Sun Microsystems, Inc. All Rights Reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Sun designates this +# particular file as subject to the "Classpath" exception as provided +# by Sun in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, +# CA 95054 USA or visit www.sun.com if you need additional information or +# have any questions. +# + +rmiregistry.usage=Utilizzo: {0} <opzioni> <porta>\ +\n\ +\ndove <opzioni> include:\ +\n -J<flag di runtime> Passa l''argomento all''interprete java +rmiregistry.port.badnumber=l''argomento della porta, {0}, non \u00e8 un numero. diff --git a/src/share/classes/sun/rmi/registry/resources/rmiregistry_ja.properties b/src/share/classes/sun/rmi/registry/resources/rmiregistry_ja.properties new file mode 100644 index 000000000..9959b187b --- /dev/null +++ b/src/share/classes/sun/rmi/registry/resources/rmiregistry_ja.properties @@ -0,0 +1,30 @@ +# +# Copyright 1999-2005 Sun Microsystems, Inc. All Rights Reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Sun designates this +# particular file as subject to the "Classpath" exception as provided +# by Sun in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, +# CA 95054 USA or visit www.sun.com if you need additional information or +# have any questions. +# + +rmiregistry.usage=\u4f7f\u3044\u65b9: {0} <options> <port>\ +\n\ +\n<options> \u306b\u306f\u6b21\u306e\u3082\u306e\u304c\u3042\u308a\u307e\u3059\u3002\ +\n -J<runtime flag> java \u30a4\u30f3\u30bf\u30d7\u30ea\u30bf\u306b\u5f15\u6570\u3092\u6e21\u3059 +rmiregistry.port.badnumber=\u30dd\u30fc\u30c8\u5f15\u6570 {0} \u306f\u756a\u53f7\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002 diff --git a/src/share/classes/sun/rmi/registry/resources/rmiregistry_ko.properties b/src/share/classes/sun/rmi/registry/resources/rmiregistry_ko.properties new file mode 100644 index 000000000..49cdf4d24 --- /dev/null +++ b/src/share/classes/sun/rmi/registry/resources/rmiregistry_ko.properties @@ -0,0 +1,31 @@ +# +# +# Copyright 2000-2005 Sun Microsystems, Inc. All Rights Reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Sun designates this +# particular file as subject to the "Classpath" exception as provided +# by Sun in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, +# CA 95054 USA or visit www.sun.com if you need additional information or +# have any questions. +# + +rmiregistry.usage=\uc0ac\uc6a9\ubc95: {0} <options> <port>\ +\n\ +\n<options>\ub294 \ub2e4\uc74c\uc744 \ud3ec\ud568\ud569\ub2c8\ub2e4:\ +\n -J<runtime flag> Java \uc778\ud130\ud504\ub9ac\ud130\uc5d0 \uc778\uc790\ub97c \uc804\ub2ec\ud569\ub2c8\ub2e4. +rmiregistry.port.badnumber=\ud3ec\ud2b8 \uc778\uc790 {0}\uc740(\ub294) \uc22b\uc790\uac00 \uc544\ub2d9\ub2c8\ub2e4. diff --git a/src/share/classes/sun/rmi/registry/resources/rmiregistry_sv.properties b/src/share/classes/sun/rmi/registry/resources/rmiregistry_sv.properties new file mode 100644 index 000000000..2d1cf052e --- /dev/null +++ b/src/share/classes/sun/rmi/registry/resources/rmiregistry_sv.properties @@ -0,0 +1,31 @@ +# +# +# Copyright 2000-2005 Sun Microsystems, Inc. All Rights Reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Sun designates this +# particular file as subject to the "Classpath" exception as provided +# by Sun in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, +# CA 95054 USA or visit www.sun.com if you need additional information or +# have any questions. +# + +rmiregistry.usage=G\u00f6r s\u00e5 h\u00e4r: {0} <alternativ> <port>\ +\n\ +\nd\u00e4r <alternativ> omfattar:\ +\n -J<k\u00f6rtidsflagga> Skicka argumentet till java-tolken +rmiregistry.port.badnumber=portargumentet, {0}, \u00e4r inte en siffra. diff --git a/src/share/classes/sun/rmi/registry/resources/rmiregistry_zh_CN.properties b/src/share/classes/sun/rmi/registry/resources/rmiregistry_zh_CN.properties new file mode 100644 index 000000000..a018932a0 --- /dev/null +++ b/src/share/classes/sun/rmi/registry/resources/rmiregistry_zh_CN.properties @@ -0,0 +1,31 @@ +# +# +# Copyright 1999-2005 Sun Microsystems, Inc. All Rights Reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Sun designates this +# particular file as subject to the "Classpath" exception as provided +# by Sun in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, +# CA 95054 USA or visit www.sun.com if you need additional information or +# have any questions. +# + +rmiregistry.usage=\u7528\u6cd5\uff1a {0} <\u9009\u9879> <\u7aef\u53e3>\ +\n\ +\n\u5176\u4e2d\uff0c<\u9009\u9879> \u5305\u62ec\uff1a\ +\n -J<runtime \u6807\u8bb0> \u5c06\u53c2\u6570\u4f20\u9012\u5230 java \u89e3\u91ca\u7a0b\u5e8f +rmiregistry.port.badnumber=\u7aef\u53e3\u53c2\u6570\uff1a{0}, \u4e0d\u662f\u6570\u5b57\u3002 diff --git a/src/share/classes/sun/rmi/registry/resources/rmiregistry_zh_TW.properties b/src/share/classes/sun/rmi/registry/resources/rmiregistry_zh_TW.properties new file mode 100644 index 000000000..41862b1c7 --- /dev/null +++ b/src/share/classes/sun/rmi/registry/resources/rmiregistry_zh_TW.properties @@ -0,0 +1,31 @@ +# +# +# Copyright 2000-2005 Sun Microsystems, Inc. All Rights Reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Sun designates this +# particular file as subject to the "Classpath" exception as provided +# by Sun in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, +# CA 95054 USA or visit www.sun.com if you need additional information or +# have any questions. +# + +rmiregistry.usage=\u7528\u6cd5: {0} <options> <port>\ +\n\ +\n\u5176\u4e2d <options> \u5305\u62ec\ufe30\ +\n -J<runtime flag> \u50b3\u905e\u5f15\u6578\u5230 java \u76f4\u8b6f\u5668 +rmiregistry.port.badnumber=\u9023\u63a5\u57e0\u5f15\u6578\uff0c{0}\uff0c\u4e0d\u662f\u4e00\u500b\u6578\u5b57 diff --git a/src/share/classes/sun/rmi/rmic/BatchEnvironment.java b/src/share/classes/sun/rmi/rmic/BatchEnvironment.java new file mode 100644 index 000000000..7e86857fb --- /dev/null +++ b/src/share/classes/sun/rmi/rmic/BatchEnvironment.java @@ -0,0 +1,445 @@ +/* + * Copyright 1996-2007 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +/*****************************************************************************/ +/* Copyright (c) IBM Corporation 1998 */ +/* */ +/* (C) Copyright IBM Corp. 1998 */ +/* */ +/*****************************************************************************/ + +package sun.rmi.rmic; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Collection; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.StringTokenizer; +import java.util.Vector; +import java.util.jar.JarFile; +import java.util.jar.Manifest; +import java.util.jar.Attributes; +import sun.tools.java.ClassPath; + +/** + * BatchEnvironment for rmic extends javac's version in four ways: + * 1. It overrides errorString() to handle looking for rmic-specific + * error messages in rmic's resource bundle + * 2. It provides a mechanism for recording intermediate generated + * files so that they can be deleted later. + * 3. It holds a reference to the Main instance so that generators + * can refer to it. + * 4. It provides access to the ClassPath passed to the constructor. + * + * WARNING: The contents of this source file are not part of any + * supported API. Code that depends on them does so at its own risk: + * they are subject to change or removal without notice. + */ + +public class BatchEnvironment extends sun.tools.javac.BatchEnvironment { + + /** instance of Main which created this environment */ + private Main main; + + /** + * Create a ClassPath object for rmic from a class path string. + */ + public static ClassPath createClassPath(String classPathString) { + ClassPath[] paths = classPaths(null, classPathString, null, null); + return paths[1]; + } + + /** + * Create a ClassPath object for rmic from the relevant command line + * options for class path, boot class path, and extension directories. + */ + public static ClassPath createClassPath(String classPathString, + String sysClassPathString, + String extDirsString) + { + /** + * Previously, this method delegated to the + * sun.tools.javac.BatchEnvironment.classPaths method in order + * to supply default values for paths not specified on the + * command line, expand extensions directories into specific + * JAR files, and construct the ClassPath object-- but as part + * of the fix for 6473331, which adds support for Class-Path + * manifest entries in JAR files, those steps are now handled + * here directly, with the help of a Path utility class copied + * from the new javac implementation (see below). + */ + Path path = new Path(); + + if (sysClassPathString == null) { + sysClassPathString = System.getProperty("sun.boot.class.path"); + } + if (sysClassPathString != null) { + path.addFiles(sysClassPathString); + } + + /* + * Class-Path manifest entries are supported for JAR files + * everywhere except in the boot class path. + */ + path.expandJarClassPaths(true); + + if (extDirsString == null) { + extDirsString = System.getProperty("java.ext.dirs"); + } + if (extDirsString != null) { + path.addDirectories(extDirsString); + } + + /* + * In the application class path, an empty element means + * the current working directory. + */ + path.emptyPathDefault("."); + + if (classPathString == null) { + // The env.class.path property is the user's CLASSPATH + // environment variable, and it set by the wrapper (ie, + // javac.exe). + classPathString = System.getProperty("env.class.path"); + if (classPathString == null) { + classPathString = "."; + } + } + path.addFiles(classPathString); + + return new ClassPath(path.toArray(new String[path.size()])); + } + + /** + * Create a BatchEnvironment for rmic with the given class path, + * stream for messages and Main. + */ + public BatchEnvironment(OutputStream out, ClassPath path, Main main) { + super(out, new ClassPath(""), path); + // use empty "sourcePath" (see 4666958) + this.main = main; + } + + /** + * Get the instance of Main which created this environment. + */ + public Main getMain() { + return main; + } + + /** + * Get the ClassPath. + */ + public ClassPath getClassPath() { + return binaryPath; + } + + /** list of generated source files created in this environment */ + private Vector generatedFiles = new Vector(); + + /** + * Remember a generated source file generated so that it + * can be removed later, if appropriate. + */ + public void addGeneratedFile(File file) { + generatedFiles.addElement(file); + } + + /** + * Delete all the generated source files made during the execution + * of this environment (those that have been registered with the + * "addGeneratedFile" method). + */ + public void deleteGeneratedFiles() { + synchronized(generatedFiles) { + Enumeration enumeration = generatedFiles.elements(); + while (enumeration.hasMoreElements()) { + File file = (File) enumeration.nextElement(); + file.delete(); + } + generatedFiles.removeAllElements(); + } + } + + /** + * Release resources, if any. + */ + public void shutdown() { + main = null; + generatedFiles = null; + super.shutdown(); + } + + /** + * Return the formatted, localized string for a named error message + * and supplied arguments. For rmic error messages, with names that + * being with "rmic.", look up the error message in rmic's resource + * bundle; otherwise, defer to java's superclass method. + */ + public String errorString(String err, + Object arg0, Object arg1, Object arg2) + { + if (err.startsWith("rmic.") || err.startsWith("warn.rmic.")) { + String result = Main.getText(err, + (arg0 != null ? arg0.toString() : null), + (arg1 != null ? arg1.toString() : null), + (arg2 != null ? arg2.toString() : null)); + + if (err.startsWith("warn.")) { + result = "warning: " + result; + } + return result; + } else { + return super.errorString(err, arg0, arg1, arg2); + } + } + public void reset() { + } + + /** + * Utility for building paths of directories and JAR files. This + * class was copied from com.sun.tools.javac.util.Paths as part of + * the fix for 6473331, which adds support for Class-Path manifest + * entries in JAR files. Diagnostic code is simply commented out + * because rmic silently ignored these conditions historically. + */ + private static class Path extends LinkedHashSet<String> { + private static final long serialVersionUID = 0; + private static final boolean warn = false; + + private static class PathIterator implements Collection<String> { + private int pos = 0; + private final String path; + private final String emptyPathDefault; + + public PathIterator(String path, String emptyPathDefault) { + this.path = path; + this.emptyPathDefault = emptyPathDefault; + } + public PathIterator(String path) { this(path, null); } + public Iterator<String> iterator() { + return new Iterator<String>() { + public boolean hasNext() { + return pos <= path.length(); + } + public String next() { + int beg = pos; + int end = path.indexOf(File.pathSeparator, beg); + if (end == -1) + end = path.length(); + pos = end + 1; + + if (beg == end && emptyPathDefault != null) + return emptyPathDefault; + else + return path.substring(beg, end); + } + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + // required for Collection. + public int size() { + throw new UnsupportedOperationException(); + } + public boolean isEmpty() { + throw new UnsupportedOperationException(); + } + public boolean contains(Object o) { + throw new UnsupportedOperationException(); + } + public Object[] toArray() { + throw new UnsupportedOperationException(); + } + public <T> T[] toArray(T[] a) { + throw new UnsupportedOperationException(); + } + public boolean add(String o) { + throw new UnsupportedOperationException(); + } + public boolean remove(Object o) { + throw new UnsupportedOperationException(); + } + public boolean containsAll(Collection<?> c) { + throw new UnsupportedOperationException(); + } + public boolean addAll(Collection<? extends String> c) { + throw new UnsupportedOperationException(); + } + public boolean removeAll(Collection<?> c) { + throw new UnsupportedOperationException(); + } + public boolean retainAll(Collection<?> c) { + throw new UnsupportedOperationException(); + } + public void clear() { + throw new UnsupportedOperationException(); + } + public boolean equals(Object o) { + throw new UnsupportedOperationException(); + } + public int hashCode() { + throw new UnsupportedOperationException(); + } + } + + /** Is this the name of a zip file? */ + private static boolean isZip(String name) { + return new File(name).isFile(); + } + + private boolean expandJarClassPaths = false; + + public Path expandJarClassPaths(boolean x) { + expandJarClassPaths = x; + return this; + } + + /** What to use when path element is the empty string */ + private String emptyPathDefault = null; + + public Path emptyPathDefault(String x) { + emptyPathDefault = x; + return this; + } + + public Path() { super(); } + + public Path addDirectories(String dirs, boolean warn) { + if (dirs != null) + for (String dir : new PathIterator(dirs)) + addDirectory(dir, warn); + return this; + } + + public Path addDirectories(String dirs) { + return addDirectories(dirs, warn); + } + + private void addDirectory(String dir, boolean warn) { + if (! new File(dir).isDirectory()) { +// if (warn) +// log.warning(Position.NOPOS, +// "dir.path.element.not.found", dir); + return; + } + + for (String direntry : new File(dir).list()) { + String canonicalized = direntry.toLowerCase(); + if (canonicalized.endsWith(".jar") || + canonicalized.endsWith(".zip")) + addFile(dir + File.separator + direntry, warn); + } + } + + public Path addFiles(String files, boolean warn) { + if (files != null) + for (String file : new PathIterator(files, emptyPathDefault)) + addFile(file, warn); + return this; + } + + public Path addFiles(String files) { + return addFiles(files, warn); + } + + private void addFile(String file, boolean warn) { + if (contains(file)) { + /* Discard duplicates and avoid infinite recursion */ + return; + } + + File ele = new File(file); + if (! ele.exists()) { + /* No such file or directory exist */ + if (warn) +// log.warning(Position.NOPOS, +// "path.element.not.found", file); + return; + } + + if (ele.isFile()) { + /* File is an ordinay file */ + String arcname = file.toLowerCase(); + if (! (arcname.endsWith(".zip") || + arcname.endsWith(".jar"))) { + /* File name don't have right extension */ +// if (warn) +// log.warning(Position.NOPOS, +// "invalid.archive.file", file); + return; + } + } + + /* Now what we have left is either a directory or a file name + confirming to archive naming convention */ + + super.add(file); + if (expandJarClassPaths && isZip(file)) + addJarClassPath(file, warn); + } + + // Adds referenced classpath elements from a jar's Class-Path + // Manifest entry. In some future release, we may want to + // update this code to recognize URLs rather than simple + // filenames, but if we do, we should redo all path-related code. + private void addJarClassPath(String jarFileName, boolean warn) { + try { + String jarParent = new File(jarFileName).getParent(); + JarFile jar = new JarFile(jarFileName); + + try { + Manifest man = jar.getManifest(); + if (man == null) return; + + Attributes attr = man.getMainAttributes(); + if (attr == null) return; + + String path = attr.getValue(Attributes.Name.CLASS_PATH); + if (path == null) return; + + for (StringTokenizer st = new StringTokenizer(path); + st.hasMoreTokens();) { + String elt = st.nextToken(); + if (jarParent != null) + elt = new File(jarParent, elt).toString(); + addFile(elt, warn); + } + } finally { + jar.close(); + } + } catch (IOException e) { +// log.error(Position.NOPOS, +// "error.reading.file", jarFileName, +// e.getLocalizedMessage()); + } + } + } +} diff --git a/src/share/classes/sun/rmi/rmic/Constants.java b/src/share/classes/sun/rmi/rmic/Constants.java new file mode 100644 index 000000000..96bef7b29 --- /dev/null +++ b/src/share/classes/sun/rmi/rmic/Constants.java @@ -0,0 +1,44 @@ +/* + * Copyright 1997-2003 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.rmic; + +import sun.tools.java.Identifier; + +/** + * WARNING: The contents of this source file are not part of any + * supported API. Code that depends on them does so at its own risk: + * they are subject to change or removal without notice. + */ +public interface Constants extends sun.tools.java.Constants { + + /* + * Identifiers potentially useful for all Generators + */ + public static final Identifier idRemote = + Identifier.lookup("java.rmi.Remote"); + public static final Identifier idRemoteException = + Identifier.lookup("java.rmi.RemoteException"); +} diff --git a/src/share/classes/sun/rmi/rmic/Generator.java b/src/share/classes/sun/rmi/rmic/Generator.java new file mode 100644 index 000000000..0126e8c03 --- /dev/null +++ b/src/share/classes/sun/rmi/rmic/Generator.java @@ -0,0 +1,79 @@ +/* + * Copyright 1998-2007 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +/* + * Licensed Materials - Property of IBM + * RMI-IIOP v1.0 + * Copyright IBM Corp. 1998 1999 All Rights Reserved + * + */ + +package sun.rmi.rmic; + +import java.io.File; +import sun.tools.java.ClassDefinition; + +/** + * Generator defines the protocol for back-end implementations to be added + * to rmic. See the rmic.properties file for a description of the format for + * adding new Generators to rmic. + * <p> + * Classes implementing this interface must have a public default constructor + * which should set any required arguments to their defaults. When Main + * encounters a command line argument which maps to a specific Generator + * subclass, it will instantiate one and call parseArgs(...). At some later + * point, Main will invoke the generate(...) method once for _each_ class passed + * on the command line. + * + * WARNING: The contents of this source file are not part of any + * supported API. Code that depends on them does so at its own risk: + * they are subject to change or removal without notice. + * + * @author Bryan Atsatt + */ +public interface Generator { + + /** + * Examine and consume command line arguments. + * @param argv The command line arguments. Ignore null + * and unknown arguments. Set each consumed argument to null. + * @param main Report any errors using the main.error() methods. + * @return true if no errors, false otherwise. + */ + public boolean parseArgs(String argv[], Main main); + + /** + * Generate output. Any source files created which need compilation should + * be added to the compiler environment using the addGeneratedFile(File) + * method. + * + * @param env The compiler environment + * @param cdef The definition for the implementation class or interface from + * which to generate output + * @param destDir The directory for the root of the package hierarchy + * for generated files. May be null. + */ + public void generate(BatchEnvironment env, ClassDefinition cdef, File destDir); +} diff --git a/src/share/classes/sun/rmi/rmic/IndentingWriter.java b/src/share/classes/sun/rmi/rmic/IndentingWriter.java new file mode 100644 index 000000000..43c879d78 --- /dev/null +++ b/src/share/classes/sun/rmi/rmic/IndentingWriter.java @@ -0,0 +1,299 @@ +/* + * Copyright 1997-2007 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +/*****************************************************************************/ +/* Copyright (c) IBM Corporation 1998 */ +/* */ +/* (C) Copyright IBM Corp. 1998 */ +/* */ +/*****************************************************************************/ + +package sun.rmi.rmic; + +import java.io.Writer; +import java.io.BufferedWriter; +import java.io.IOException; + +/** + * IndentingWriter is a BufferedWriter subclass that supports automatic + * indentation of lines of text written to the underlying Writer. + * + * Methods are provided for compact, convenient indenting, writing text, + * and writing lines in various combinations. + * + * WARNING: The contents of this source file are not part of any + * supported API. Code that depends on them does so at its own risk: + * they are subject to change or removal without notice. + */ +public class IndentingWriter extends BufferedWriter { + + /** true if the next character written is the first on a line */ + private boolean beginningOfLine = true; + + /** current number of spaces to prepend to lines */ + private int currentIndent = 0; + + /** number of spaces to change indent when indenting in or out */ + private int indentStep = 4; + + /** number of spaces to convert into tabs. Use MAX_VALUE to disable */ + private int tabSize = 8; + + /** + * Create a new IndentingWriter that writes indented text to the + * given Writer. Use the default indent step of four spaces. + */ + public IndentingWriter(Writer out) { + super(out); + } + + /** + * Create a new IndentingWriter that writes indented text to the + * given Writer and uses the supplied indent step. + */ + public IndentingWriter(Writer out, int step) { + this(out); + + if (indentStep < 0) + throw new IllegalArgumentException("negative indent step"); + + indentStep = step; + } + + /** + * Create a new IndentingWriter that writes indented text to the + * given Writer and uses the supplied indent step and tab size. + */ + public IndentingWriter(Writer out, int step, int tabSize) { + this(out); + + if (indentStep < 0) + throw new IllegalArgumentException("negative indent step"); + + indentStep = step; + this.tabSize = tabSize; + } + + /** + * Write a single character. + */ + public void write(int c) throws IOException { + checkWrite(); + super.write(c); + } + + /** + * Write a portion of an array of characters. + */ + public void write(char[] cbuf, int off, int len) throws IOException { + if (len > 0) { + checkWrite(); + } + super.write(cbuf, off, len); + } + + /** + * Write a portion of a String. + */ + public void write(String s, int off, int len) throws IOException { + if (len > 0) { + checkWrite(); + } + super.write(s, off, len); + } + + /** + * Write a line separator. The next character written will be + * preceded by an indent. + */ + public void newLine() throws IOException { + super.newLine(); + beginningOfLine = true; + } + + /** + * Check if an indent needs to be written before writing the next + * character. + * + * The indent generation is optimized (and made consistent with + * certain coding conventions) by condensing groups of eight spaces + * into tab characters. + */ + protected void checkWrite() throws IOException { + if (beginningOfLine) { + beginningOfLine = false; + int i = currentIndent; + while (i >= tabSize) { + super.write('\t'); + i -= tabSize; + } + while (i > 0) { + super.write(' '); + -- i; + } + } + } + + /** + * Increase the current indent by the indent step. + */ + protected void indentIn() { + currentIndent += indentStep; + } + + /** + * Decrease the current indent by the indent step. + */ + protected void indentOut() { + currentIndent -= indentStep; + if (currentIndent < 0) + currentIndent = 0; + } + + /** + * Indent in. + */ + public void pI() { + indentIn(); + } + + /** + * Indent out. + */ + public void pO() { + indentOut(); + } + + /** + * Write string. + */ + public void p(String s) throws IOException { + write(s); + } + + /** + * End current line. + */ + public void pln() throws IOException { + newLine(); + } + + /** + * Write string; end current line. + */ + public void pln(String s) throws IOException { + p(s); + pln(); + } + + /** + * Write string; end current line; indent in. + */ + public void plnI(String s) throws IOException { + p(s); + pln(); + pI(); + } + + /** + * Indent out; write string. + */ + public void pO(String s) throws IOException { + pO(); + p(s); + } + + /** + * Indent out; write string; end current line. + */ + public void pOln(String s) throws IOException { + pO(s); + pln(); + } + + /** + * Indent out; write string; end current line; indent in. + * + * This method is useful for generating lines of code that both + * end and begin nested blocks, like "} else {". + */ + public void pOlnI(String s) throws IOException { + pO(s); + pln(); + pI(); + } + + /** + * Write Object. + */ + public void p(Object o) throws IOException { + write(o.toString()); + } + /** + * Write Object; end current line. + */ + public void pln(Object o) throws IOException { + p(o.toString()); + pln(); + } + + /** + * Write Object; end current line; indent in. + */ + public void plnI(Object o) throws IOException { + p(o.toString()); + pln(); + pI(); + } + + /** + * Indent out; write Object. + */ + public void pO(Object o) throws IOException { + pO(); + p(o.toString()); + } + + /** + * Indent out; write Object; end current line. + */ + public void pOln(Object o) throws IOException { + pO(o.toString()); + pln(); + } + + /** + * Indent out; write Object; end current line; indent in. + * + * This method is useful for generating lines of code that both + * end and begin nested blocks, like "} else {". + */ + public void pOlnI(Object o) throws IOException { + pO(o.toString()); + pln(); + pI(); + } + +} diff --git a/src/share/classes/sun/rmi/rmic/Main.java b/src/share/classes/sun/rmi/rmic/Main.java new file mode 100644 index 000000000..bb9719aaf --- /dev/null +++ b/src/share/classes/sun/rmi/rmic/Main.java @@ -0,0 +1,884 @@ +/* + * Copyright 1996-2007 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +/* + * Licensed Materials - Property of IBM + * RMI-IIOP v1.0 + * Copyright IBM Corp. 1998 1999 All Rights Reserved + * + */ + +package sun.rmi.rmic; + +import java.util.Vector; +import java.util.Enumeration; +import java.util.ResourceBundle; +import java.util.StringTokenizer; +import java.util.MissingResourceException; + +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.IOException; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.ByteArrayOutputStream; + +import sun.tools.java.ClassFile; +import sun.tools.java.ClassDefinition; +import sun.tools.java.ClassDeclaration; +import sun.tools.java.ClassNotFound; +import sun.tools.java.Identifier; +import sun.tools.java.ClassPath; + +import sun.tools.javac.SourceClass; +import sun.tools.util.CommandLine; +import java.lang.reflect.Constructor; +import java.util.Properties; + +/** + * Main "rmic" program. + * + * WARNING: The contents of this source file are not part of any + * supported API. Code that depends on them does so at its own risk: + * they are subject to change or removal without notice. + */ +public class Main implements sun.rmi.rmic.Constants { + String sourcePathArg; + String sysClassPathArg; + String extDirsArg; + String classPathString; + File destDir; + int flags; + long tm; + Vector classes; + boolean nowrite; + boolean nocompile; + boolean keepGenerated; + boolean status; + String[] generatorArgs; + Vector generators; + Class environmentClass = BatchEnvironment.class; + boolean iiopGeneration = false; + + /** + * Name of the program. + */ + String program; + + /** + * The stream where error message are printed. + */ + OutputStream out; + + /** + * Constructor. + */ + public Main(OutputStream out, String program) { + this.out = out; + this.program = program; + } + + /** + * Output a message. + */ + public void output(String msg) { + PrintStream out = + this.out instanceof PrintStream ? (PrintStream)this.out + : new PrintStream(this.out, true); + out.println(msg); + } + + /** + * Top level error message. This method is called when the + * environment could not be set up yet. + */ + public void error(String msg) { + output(getText(msg)); + } + + public void error(String msg, String arg1) { + output(getText(msg, arg1)); + } + + public void error(String msg, String arg1, String arg2) { + output(getText(msg, arg1, arg2)); + } + + /** + * Usage + */ + public void usage() { + error("rmic.usage", program); + } + + /** + * Run the compiler + */ + public synchronized boolean compile(String argv[]) { + + /* + * Handle internal option to use the new (and incomplete) rmic + * implementation. This option is handled here, rather than + * in parseArgs, so that none of the arguments will be nulled + * before delegating to the new implementation. + */ + for (int i = 0; i < argv.length; i++) { + if (argv[i].equals("-Xnew")) { + return (new sun.rmi.rmic.newrmic.Main(out, + program)).compile(argv); + } + } + + if (!parseArgs(argv)) { + return false; + } + + if (classes.size() == 0) { + usage(); + return false; + } + + return doCompile(); + } + + /** + * Get the destination directory. + */ + public File getDestinationDir() { + return destDir; + } + + /** + * Parse the arguments for compile. + */ + public boolean parseArgs(String argv[]) { + sourcePathArg = null; + sysClassPathArg = null; + extDirsArg = null; + + classPathString = null; + destDir = null; + flags = F_WARNINGS; + tm = System.currentTimeMillis(); + classes = new Vector(); + nowrite = false; + nocompile = false; + keepGenerated = false; + generatorArgs = getArray("generator.args",true); + if (generatorArgs == null) { + return false; + } + generators = new Vector(); + + // Pre-process command line for @file arguments + try { + argv = CommandLine.parse(argv); + } catch (FileNotFoundException e) { + error("rmic.cant.read", e.getMessage()); + return false; + } catch (IOException e) { + e.printStackTrace(out instanceof PrintStream ? + (PrintStream) out : + new PrintStream(out, true)); + return false; + } + + // Parse arguments + for (int i = 0 ; i < argv.length ; i++) { + if (argv[i] != null) { + if (argv[i].equals("-g")) { + flags &= ~F_OPT; + flags |= F_DEBUG_LINES | F_DEBUG_VARS; + argv[i] = null; + } else if (argv[i].equals("-O")) { + flags &= ~F_DEBUG_LINES; + flags &= ~F_DEBUG_VARS; + flags |= F_OPT | F_DEPENDENCIES; + argv[i] = null; + } else if (argv[i].equals("-nowarn")) { + flags &= ~F_WARNINGS; + argv[i] = null; + } else if (argv[i].equals("-debug")) { + flags |= F_DUMP; + argv[i] = null; + } else if (argv[i].equals("-depend")) { + flags |= F_DEPENDENCIES; + argv[i] = null; + } else if (argv[i].equals("-verbose")) { + flags |= F_VERBOSE; + argv[i] = null; + } else if (argv[i].equals("-nowrite")) { + nowrite = true; + argv[i] = null; + } else if (argv[i].equals("-Xnocompile")) { + nocompile = true; + keepGenerated = true; + argv[i] = null; + } else if (argv[i].equals("-keep") || + argv[i].equals("-keepgenerated")) { + keepGenerated = true; + argv[i] = null; + } else if (argv[i].equals("-show")) { + error("rmic.option.unsupported", "-show"); + usage(); + return false; + } else if (argv[i].equals("-classpath")) { + if ((i + 1) < argv.length) { + if (classPathString != null) { + error("rmic.option.already.seen", "-classpath"); + usage(); + return false; + } + argv[i] = null; + classPathString = argv[++i]; + argv[i] = null; + } else { + error("rmic.option.requires.argument", "-classpath"); + usage(); + return false; + } + } else if (argv[i].equals("-sourcepath")) { + if ((i + 1) < argv.length) { + if (sourcePathArg != null) { + error("rmic.option.already.seen", "-sourcepath"); + usage(); + return false; + } + argv[i] = null; + sourcePathArg = argv[++i]; + argv[i] = null; + } else { + error("rmic.option.requires.argument", "-sourcepath"); + usage(); + return false; + } + } else if (argv[i].equals("-bootclasspath")) { + if ((i + 1) < argv.length) { + if (sysClassPathArg != null) { + error("rmic.option.already.seen", "-bootclasspath"); + usage(); + return false; + } + argv[i] = null; + sysClassPathArg = argv[++i]; + argv[i] = null; + } else { + error("rmic.option.requires.argument", "-bootclasspath"); + usage(); + return false; + } + } else if (argv[i].equals("-extdirs")) { + if ((i + 1) < argv.length) { + if (extDirsArg != null) { + error("rmic.option.already.seen", "-extdirs"); + usage(); + return false; + } + argv[i] = null; + extDirsArg = argv[++i]; + argv[i] = null; + } else { + error("rmic.option.requires.argument", "-extdirs"); + usage(); + return false; + } + } else if (argv[i].equals("-d")) { + if ((i + 1) < argv.length) { + if (destDir != null) { + error("rmic.option.already.seen", "-d"); + usage(); + return false; + } + argv[i] = null; + destDir = new File(argv[++i]); + argv[i] = null; + if (!destDir.exists()) { + error("rmic.no.such.directory", destDir.getPath()); + usage(); + return false; + } + } else { + error("rmic.option.requires.argument", "-d"); + usage(); + return false; + } + } else { + if (!checkGeneratorArg(argv,i)) { + usage(); + return false; + } + } + } + } + + + // Now that all generators have had a chance at the args, + // scan what's left for classes and illegal args... + + for (int i = 0; i < argv.length; i++) { + if (argv[i] != null) { + if (argv[i].startsWith("-")) { + error("rmic.no.such.option", argv[i]); + usage(); + return false; + } else { + classes.addElement(argv[i]); + } + } + } + + + // If the generators vector is empty, add the default generator... + + if (generators.size() == 0) { + addGenerator("default"); + } + + return true; + } + + /** + * If this argument is for a generator, instantiate it, call + * parseArgs(...) and add generator to generators vector. + * Returns false on error. + */ + protected boolean checkGeneratorArg(String[] argv, int currentIndex) { + boolean result = true; + if (argv[currentIndex].startsWith("-")) { + String arg = argv[currentIndex].substring(1).toLowerCase(); // Remove '-' + for (int i = 0; i < generatorArgs.length; i++) { + if (arg.equalsIgnoreCase(generatorArgs[i])) { + // Got a match, add Generator and call parseArgs... + Generator gen = addGenerator(arg); + if (gen == null) { + return false; + } + result = gen.parseArgs(argv,this); + break; + } + } + } + return result; + } + + /** + * Instantiate and add a generator to the generators array. + */ + protected Generator addGenerator(String arg) { + + Generator gen; + + // Create an instance of the generator and add it to + // the array... + + String className = getString("generator.class." + arg); + if (className == null) { + error("rmic.missing.property",arg); + return null; + } + + try { + gen = (Generator) Class.forName(className).newInstance(); + } catch (Exception e) { + error("rmic.cannot.instantiate",className); + return null; + } + + generators.addElement(gen); + + // Get the environment required by this generator... + + Class envClass = BatchEnvironment.class; + String env = getString("generator.env." + arg); + if (env != null) { + try { + envClass = Class.forName(env); + + // Is the new class a subclass of the current one? + + if (environmentClass.isAssignableFrom(envClass)) { + + // Yes, so switch to the new one... + + environmentClass = envClass; + + } else { + + // No. Is the current class a subclass of the + // new one? + + if (!envClass.isAssignableFrom(environmentClass)) { + + // No, so it's a conflict... + + error("rmic.cannot.use.both",environmentClass.getName(),envClass.getName()); + return null; + } + } + } catch (ClassNotFoundException e) { + error("rmic.class.not.found",env); + return null; + } + } + + // If this is the iiop stub generator, cache + // that fact for the jrmp generator... + + if (arg.equals("iiop")) { + iiopGeneration = true; + } + return gen; + } + + /** + * Grab a resource string and parse it into an array of strings. Assumes + * comma separated list. + * @param name The resource name. + * @param mustExist If true, throws error if resource does not exist. If + * false and resource does not exist, returns zero element array. + */ + protected String[] getArray(String name, boolean mustExist) { + String[] result = null; + String value = getString(name); + if (value == null) { + if (mustExist) { + error("rmic.resource.not.found",name); + return null; + } else { + return new String[0]; + } + } + + StringTokenizer parser = new StringTokenizer(value,", \t\n\r", false); + int count = parser.countTokens(); + result = new String[count]; + for (int i = 0; i < count; i++) { + result[i] = parser.nextToken(); + } + + return result; + } + + /** + * Get the correct type of BatchEnvironment + */ + public BatchEnvironment getEnv() { + + ClassPath classPath = + BatchEnvironment.createClassPath(classPathString, + sysClassPathArg, + extDirsArg); + BatchEnvironment result = null; + try { + Class[] ctorArgTypes = {OutputStream.class,ClassPath.class,Main.class}; + Object[] ctorArgs = {out,classPath,this}; + Constructor constructor = environmentClass.getConstructor(ctorArgTypes); + result = (BatchEnvironment) constructor.newInstance(ctorArgs); + result.reset(); + } + catch (Exception e) { + error("rmic.cannot.instantiate",environmentClass.getName()); + } + return result; + } + + + /** + * Do the compile with the switches and files already supplied + */ + public boolean doCompile() { + // Create batch environment + BatchEnvironment env = getEnv(); + env.flags |= flags; + + // Set the classfile version numbers + // Compat and 1.1 stubs must retain the old version number. + env.majorVersion = 45; + env.minorVersion = 3; + + // Preload the "out of memory" error string just in case we run + // out of memory during the compile. + String noMemoryErrorString = getText("rmic.no.memory"); + String stackOverflowErrorString = getText("rmic.stack.overflow"); + + try { + /** Load the classes on the command line + * Replace the entries in classes with the ClassDefinition for the class + */ + for (int i = classes.size()-1; i >= 0; i-- ) { + Identifier implClassName = + Identifier.lookup((String)classes.elementAt(i)); + + /* + * Fix bugid 4049354: support using '.' as an inner class + * qualifier on the command line (previously, only mangled + * inner class names were understood, like "pkg.Outer$Inner"). + * + * The following method, also used by "javap", resolves the + * given unmangled inner class name to the appropriate + * internal identifier. For example, it translates + * "pkg.Outer.Inner" to "pkg.Outer. Inner". + */ + implClassName = env.resolvePackageQualifiedName(implClassName); + /* + * But if we use such an internal inner class name identifier + * to load the class definition, the Java compiler will notice + * if the impl class is a "private" inner class and then deny + * skeletons (needed unless "-v1.2" is used) the ability to + * cast to it. To work around this problem, we mangle inner + * class name identifiers to their binary "outer" class name: + * "pkg.Outer. Inner" becomes "pkg.Outer$Inner". + */ + implClassName = Names.mangleClass(implClassName); + + ClassDeclaration decl = env.getClassDeclaration(implClassName); + try { + ClassDefinition def = decl.getClassDefinition(env); + for (int j = 0; j < generators.size(); j++) { + Generator gen = (Generator)generators.elementAt(j); + gen.generate(env, def, destDir); + } + } catch (ClassNotFound ex) { + env.error(0, "rmic.class.not.found", implClassName); + } + + } + + // compile all classes that need compilation + if (!nocompile) { + compileAllClasses(env); + } + } catch (OutOfMemoryError ee) { + // The compiler has run out of memory. Use the error string + // which we preloaded. + env.output(noMemoryErrorString); + return false; + } catch (StackOverflowError ee) { + env.output(stackOverflowErrorString); + return false; + } catch (Error ee) { + // We allow the compiler to take an exception silently if a program + // error has previously been detected. Presumably, this makes the + // compiler more robust in the face of bad error recovery. + if (env.nerrors == 0 || env.dump()) { + env.error(0, "fatal.error"); + ee.printStackTrace(out instanceof PrintStream ? + (PrintStream) out : + new PrintStream(out, true)); + } + } catch (Exception ee) { + if (env.nerrors == 0 || env.dump()) { + env.error(0, "fatal.exception"); + ee.printStackTrace(out instanceof PrintStream ? + (PrintStream) out : + new PrintStream(out, true)); + } + } + + env.flushErrors(); + + boolean status = true; + if (env.nerrors > 0) { + String msg = ""; + if (env.nerrors > 1) { + msg = getText("rmic.errors", env.nerrors); + } else { + msg = getText("rmic.1error"); + } + if (env.nwarnings > 0) { + if (env.nwarnings > 1) { + msg += ", " + getText("rmic.warnings", env.nwarnings); + } else { + msg += ", " + getText("rmic.1warning"); + } + } + output(msg); + status = false; + } else { + if (env.nwarnings > 0) { + if (env.nwarnings > 1) { + output(getText("rmic.warnings", env.nwarnings)); + } else { + output(getText("rmic.1warning")); + } + } + } + + // last step is to delete generated source files + if (!keepGenerated) { + env.deleteGeneratedFiles(); + } + + // We're done + if (env.verbose()) { + tm = System.currentTimeMillis() - tm; + output(getText("rmic.done_in", Long.toString(tm))); + } + + // Shutdown the environment object and release our resources. + // Note that while this is unneccessary when rmic is invoked + // the command line, there are environments in which rmic + // from is invoked within a server process, so resource + // reclamation is important... + + env.shutdown(); + + sourcePathArg = null; + sysClassPathArg = null; + extDirsArg = null; + classPathString = null; + destDir = null; + classes = null; + generatorArgs = null; + generators = null; + environmentClass = null; + program = null; + out = null; + + return status; + } + + /* + * Compile all classes that need to be compiled. + */ + public void compileAllClasses (BatchEnvironment env) + throws ClassNotFound, + IOException, + InterruptedException { + ByteArrayOutputStream buf = new ByteArrayOutputStream(4096); + boolean done; + + do { + done = true; + for (Enumeration e = env.getClasses() ; e.hasMoreElements() ; ) { + ClassDeclaration c = (ClassDeclaration)e.nextElement(); + done = compileClass(c,buf,env); + } + } while (!done); + } + + /* + * Compile a single class. + */ + public boolean compileClass (ClassDeclaration c, + ByteArrayOutputStream buf, + BatchEnvironment env) + throws ClassNotFound, + IOException, + InterruptedException { + boolean done = true; + env.flushErrors(); + SourceClass src; + + switch (c.getStatus()) { + case CS_UNDEFINED: + { + if (!env.dependencies()) { + break; + } + // fall through + } + + case CS_SOURCE: + { + done = false; + env.loadDefinition(c); + if (c.getStatus() != CS_PARSED) { + break; + } + // fall through + } + + case CS_PARSED: + { + if (c.getClassDefinition().isInsideLocal()) { + break; + } + // If we get to here, then compilation is going + // to occur. If the -Xnocompile switch is set + // then fail. Note that this check is required + // here because this method is called from + // generators, not just from within this class... + + if (nocompile) { + throw new IOException("Compilation required, but -Xnocompile option in effect"); + } + + done = false; + + src = (SourceClass)c.getClassDefinition(env); + src.check(env); + c.setDefinition(src, CS_CHECKED); + // fall through + } + + case CS_CHECKED: + { + src = (SourceClass)c.getClassDefinition(env); + // bail out if there were any errors + if (src.getError()) { + c.setDefinition(src, CS_COMPILED); + break; + } + done = false; + buf.reset(); + src.compile(buf); + c.setDefinition(src, CS_COMPILED); + src.cleanup(env); + + if (src.getError() || nowrite) { + break; + } + + String pkgName = c.getName().getQualifier().toString().replace('.', File.separatorChar); + String className = c.getName().getFlatName().toString().replace('.', SIGC_INNERCLASS) + ".class"; + + File file; + if (destDir != null) { + if (pkgName.length() > 0) { + file = new File(destDir, pkgName); + if (!file.exists()) { + file.mkdirs(); + } + file = new File(file, className); + } else { + file = new File(destDir, className); + } + } else { + ClassFile classfile = (ClassFile)src.getSource(); + if (classfile.isZipped()) { + env.error(0, "cant.write", classfile.getPath()); + break; + } + file = new File(classfile.getPath()); + file = new File(file.getParent(), className); + } + + // Create the file + try { + FileOutputStream out = new FileOutputStream(file.getPath()); + buf.writeTo(out); + out.close(); + if (env.verbose()) { + output(getText("rmic.wrote", file.getPath())); + } + } catch (IOException ee) { + env.error(0, "cant.write", file.getPath()); + } + } + } + return done; + } + + /** + * Main program + */ + public static void main(String argv[]) { + Main compiler = new Main(System.out, "rmic"); + System.exit(compiler.compile(argv) ? 0 : 1); + } + + /** + * Return the string value of a named resource in the rmic.properties + * resource bundle. If the resource is not found, null is returned. + */ + public static String getString(String key) { + if (!resourcesInitialized) { + initResources(); + } + + // To enable extensions, search the 'resourcesExt' + // bundle first, followed by the 'resources' bundle... + + if (resourcesExt != null) { + try { + return resourcesExt.getString(key); + } catch (MissingResourceException e) {} + } + + try { + return resources.getString(key); + } catch (MissingResourceException ignore) { + } + return null; + } + + private static boolean resourcesInitialized = false; + private static ResourceBundle resources; + private static ResourceBundle resourcesExt = null; + + private static void initResources() { + try { + resources = + ResourceBundle.getBundle("sun.rmi.rmic.resources.rmic"); + resourcesInitialized = true; + try { + resourcesExt = + ResourceBundle.getBundle("sun.rmi.rmic.resources.rmicext"); + } catch (MissingResourceException e) {} + } catch (MissingResourceException e) { + throw new Error("fatal: missing resource bundle: " + + e.getClassName()); + } + } + + public static String getText(String key) { + String message = getString(key); + if (message == null) { + message = "no text found: \"" + key + "\""; + } + return message; + } + + public static String getText(String key, int num) { + return getText(key, Integer.toString(num), null, null); + } + + public static String getText(String key, String arg0) { + return getText(key, arg0, null, null); + } + + public static String getText(String key, String arg0, String arg1) { + return getText(key, arg0, arg1, null); + } + + public static String getText(String key, + String arg0, String arg1, String arg2) + { + String format = getString(key); + if (format == null) { + format = "no text found: key = \"" + key + "\", " + + "arguments = \"{0}\", \"{1}\", \"{2}\""; + } + + String[] args = new String[3]; + args[0] = (arg0 != null ? arg0.toString() : "null"); + args[1] = (arg1 != null ? arg1.toString() : "null"); + args[2] = (arg2 != null ? arg2.toString() : "null"); + + return java.text.MessageFormat.format(format, args); + } +} diff --git a/src/share/classes/sun/rmi/rmic/Names.java b/src/share/classes/sun/rmi/rmic/Names.java new file mode 100644 index 000000000..05bb4b2e3 --- /dev/null +++ b/src/share/classes/sun/rmi/rmic/Names.java @@ -0,0 +1,88 @@ +/* + * Copyright 1996-2003 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.rmic; + +import sun.tools.java.Identifier; + +/** + * Names provides static utility methods used by other rmic classes + * for dealing with identifiers. + * + * WARNING: The contents of this source file are not part of any + * supported API. Code that depends on them does so at its own risk: + * they are subject to change or removal without notice. + */ +public class Names { + + /** + * Return stub class name for impl class name. + */ + static final public Identifier stubFor(Identifier name) { + return Identifier.lookup(name + "_Stub"); + } + + /** + * Return skeleton class name for impl class name. + */ + static final public Identifier skeletonFor(Identifier name) { + return Identifier.lookup(name + "_Skel"); + } + + /** + * If necessary, convert a class name to its mangled form, i.e. the + * non-inner class name used in the binary representation of + * inner classes. This is necessary to be able to name inner + * classes in the generated source code in places where the language + * does not permit it, such as when synthetically defining an inner + * class outside of its outer class, and for generating file names + * corresponding to inner classes. + * + * Currently this mangling involves modifying the internal names of + * inner classes by converting occurrences of ". " into "$". + * + * This code is taken from the "mangleInnerType" method of + * the "sun.tools.java.Type" class; this method cannot be accessed + * itself because it is package protected. + */ + static final public Identifier mangleClass(Identifier className) { + if (!className.isInner()) + return className; + + /* + * Get '.' qualified inner class name (with outer class + * qualification and no package qualification) and replace + * each '.' with '$'. + */ + Identifier mangled = Identifier.lookup( + className.getFlatName().toString() + .replace('.', sun.tools.java.Constants.SIGC_INNERCLASS)); + if (mangled.isInner()) + throw new Error("failed to mangle inner class name"); + + // prepend package qualifier back for returned identifier + return Identifier.lookup(className.getQualifier(), mangled); + } +} diff --git a/src/share/classes/sun/rmi/rmic/RMIConstants.java b/src/share/classes/sun/rmi/rmic/RMIConstants.java new file mode 100644 index 000000000..b28ac105b --- /dev/null +++ b/src/share/classes/sun/rmi/rmic/RMIConstants.java @@ -0,0 +1,80 @@ +/* + * Copyright 1998-2007 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +/* + * Licensed Materials - Property of IBM + * RMI-IIOP v1.0 + * Copyright IBM Corp. 1998 1999 All Rights Reserved + * + */ + +package sun.rmi.rmic; + +import sun.tools.java.Identifier; + +/** + * WARNING: The contents of this source file are not part of any + * supported API. Code that depends on them does so at its own risk: + * they are subject to change or removal without notice. + */ +public interface RMIConstants extends sun.rmi.rmic.Constants { + + /* + * identifiers for RMI classes referenced by rmic + */ + public static final Identifier idRemoteObject = + Identifier.lookup("java.rmi.server.RemoteObject"); + public static final Identifier idRemoteStub = + Identifier.lookup("java.rmi.server.RemoteStub"); + public static final Identifier idRemoteRef = + Identifier.lookup("java.rmi.server.RemoteRef"); + public static final Identifier idOperation = + Identifier.lookup("java.rmi.server.Operation"); + public static final Identifier idSkeleton = + Identifier.lookup("java.rmi.server.Skeleton"); + public static final Identifier idSkeletonMismatchException = + Identifier.lookup("java.rmi.server.SkeletonMismatchException"); + public static final Identifier idRemoteCall = + Identifier.lookup("java.rmi.server.RemoteCall"); + public static final Identifier idMarshalException = + Identifier.lookup("java.rmi.MarshalException"); + public static final Identifier idUnmarshalException = + Identifier.lookup("java.rmi.UnmarshalException"); + public static final Identifier idUnexpectedException = + Identifier.lookup("java.rmi.UnexpectedException"); + + /* + * stub protocol versions + */ + public static final int STUB_VERSION_1_1 = 1; + public static final int STUB_VERSION_FAT = 2; + public static final int STUB_VERSION_1_2 = 3; + + /** serialVersionUID for all stubs that can use 1.2 protocol */ + public static final long STUB_SERIAL_VERSION_UID = 2; + + /** version number used to seed interface hash computation */ + public static final int INTERFACE_HASH_STUB_VERSION = 1; +} diff --git a/src/share/classes/sun/rmi/rmic/RMIGenerator.java b/src/share/classes/sun/rmi/rmic/RMIGenerator.java new file mode 100644 index 000000000..41e94ff36 --- /dev/null +++ b/src/share/classes/sun/rmi/rmic/RMIGenerator.java @@ -0,0 +1,1303 @@ +/* + * Copyright 1997-2007 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +/*****************************************************************************/ +/* Copyright (c) IBM Corporation 1998 */ +/* */ +/* (C) Copyright IBM Corp. 1998 */ +/* */ +/*****************************************************************************/ + +package sun.rmi.rmic; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStreamWriter; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import sun.tools.java.Type; +import sun.tools.java.Identifier; +import sun.tools.java.ClassDefinition; +import sun.tools.java.ClassDeclaration; +import sun.tools.java.ClassNotFound; +import sun.tools.java.ClassFile; +import sun.tools.java.MemberDefinition; +import com.sun.corba.se.impl.util.Utility; + +/** + * A Generator object will generate the Java source code of the stub + * and skeleton classes for an RMI remote implementation class, using + * a particular stub protocol version. + * + * WARNING: The contents of this source file are not part of any + * supported API. Code that depends on them does so at its own risk: + * they are subject to change or removal without notice. + * + * @author Peter Jones, Bryan Atsatt + */ +public class RMIGenerator implements RMIConstants, Generator { + + private static final Hashtable versionOptions = new Hashtable(); + static { + versionOptions.put("-v1.1", new Integer(STUB_VERSION_1_1)); + versionOptions.put("-vcompat", new Integer(STUB_VERSION_FAT)); + versionOptions.put("-v1.2", new Integer(STUB_VERSION_1_2)); + } + + /** + * Default constructor for Main to use. + */ + public RMIGenerator() { + version = STUB_VERSION_1_2; // default is -v1.2 (see 4638155) + } + + /** + * Examine and consume command line arguments. + * @param argv The command line arguments. Ignore null + * and unknown arguments. Set each consumed argument to null. + * @param error Report any errors using the main.error() methods. + * @return true if no errors, false otherwise. + */ + public boolean parseArgs(String argv[], Main main) { + String explicitVersion = null; + for (int i = 0; i < argv.length; i++) { + if (argv[i] != null) { + String arg = argv[i].toLowerCase(); + if (versionOptions.containsKey(arg)) { + if (explicitVersion != null && + !explicitVersion.equals(arg)) + { + main.error("rmic.cannot.use.both", + explicitVersion, arg); + return false; + } + explicitVersion = arg; + version = ((Integer) versionOptions.get(arg)).intValue(); + argv[i] = null; + } + } + } + return true; + } + + /** + * Generate the source files for the stub and/or skeleton classes + * needed by RMI for the given remote implementation class. + * + * @param env compiler environment + * @param cdef definition of remote implementation class + * to generate stubs and/or skeletons for + * @param destDir directory for the root of the package hierarchy + * for generated files + */ + public void generate(BatchEnvironment env, ClassDefinition cdef, File destDir) { + RemoteClass remoteClass = RemoteClass.forClass(env, cdef); + if (remoteClass == null) // exit if an error occurred + return; + + RMIGenerator gen; + try { + gen = new RMIGenerator(env, cdef, destDir, remoteClass, version); + } catch (ClassNotFound e) { + env.error(0, "rmic.class.not.found", e.name); + return; + } + gen.generate(); + } + + private void generate() { + env.addGeneratedFile(stubFile); + + try { + IndentingWriter out = new IndentingWriter( + new OutputStreamWriter(new FileOutputStream(stubFile))); + writeStub(out); + out.close(); + if (env.verbose()) { + env.output(Main.getText("rmic.wrote", stubFile.getPath())); + } + env.parseFile(new ClassFile(stubFile)); + } catch (IOException e) { + env.error(0, "cant.write", stubFile.toString()); + return; + } + + if (version == STUB_VERSION_1_1 || + version == STUB_VERSION_FAT) + { + env.addGeneratedFile(skeletonFile); + + try { + IndentingWriter out = new IndentingWriter( + new OutputStreamWriter( + new FileOutputStream(skeletonFile))); + writeSkeleton(out); + out.close(); + if (env.verbose()) { + env.output(Main.getText("rmic.wrote", + skeletonFile.getPath())); + } + env.parseFile(new ClassFile(skeletonFile)); + } catch (IOException e) { + env.error(0, "cant.write", stubFile.toString()); + return; + } + } else { + /* + * For bugid 4135136: if skeleton files are not being generated + * for this compilation run, delete old skeleton source or class + * files for this remote implementation class that were + * (presumably) left over from previous runs, to avoid user + * confusion from extraneous or inconsistent generated files. + */ + + File outputDir = Util.getOutputDirectoryFor(remoteClassName,destDir,env); + File skeletonClassFile = new File(outputDir,skeletonClassName.getName().toString() + ".class"); + + skeletonFile.delete(); // ignore failures (no big deal) + skeletonClassFile.delete(); + } + } + + /** + * Return the File object that should be used as the source file + * for the given Java class, using the supplied destination + * directory for the top of the package hierarchy. + */ + protected static File sourceFileForClass(Identifier className, + Identifier outputClassName, + File destDir, + BatchEnvironment env) + { + File packageDir = Util.getOutputDirectoryFor(className,destDir,env); + String outputName = Names.mangleClass(outputClassName).getName().toString(); + + // Is there any existing _Tie equivalent leftover from a + // previous invocation of rmic -iiop? Only do this once per + // class by looking for skeleton generation... + + if (outputName.endsWith("_Skel")) { + String classNameStr = className.getName().toString(); + File temp = new File(packageDir, Utility.tieName(classNameStr) + ".class"); + if (temp.exists()) { + + // Found a tie. Is IIOP generation also being done? + + if (!env.getMain().iiopGeneration) { + + // No, so write a warning... + + env.error(0,"warn.rmic.tie.found", + classNameStr, + temp.getAbsolutePath()); + } + } + } + + String outputFileName = outputName + ".java"; + return new File(packageDir, outputFileName); + } + + + /** rmic environment for this object */ + private BatchEnvironment env; + + /** the remote class that this instance is generating code for */ + private RemoteClass remoteClass; + + /** version of the stub protocol to use in code generation */ + private int version; + + /** remote methods for remote class, indexed by operation number */ + private RemoteClass.Method[] remoteMethods; + + /** + * Names for the remote class and the stub and skeleton classes + * to be generated for it. + */ + private Identifier remoteClassName; + private Identifier stubClassName; + private Identifier skeletonClassName; + + private ClassDefinition cdef; + private File destDir; + private File stubFile; + private File skeletonFile; + + /** + * Names to use for the java.lang.reflect.Method static fields + * corresponding to each remote method. + */ + private String[] methodFieldNames; + + /** cached definition for certain exception classes in this environment */ + private ClassDefinition defException; + private ClassDefinition defRemoteException; + private ClassDefinition defRuntimeException; + + /** + * Create a new stub/skeleton Generator object for the given + * remote implementation class to generate code according to + * the given stub protocol version. + */ + private RMIGenerator(BatchEnvironment env, ClassDefinition cdef, + File destDir, RemoteClass remoteClass, int version) + throws ClassNotFound + { + this.destDir = destDir; + this.cdef = cdef; + this.env = env; + this.remoteClass = remoteClass; + this.version = version; + + remoteMethods = remoteClass.getRemoteMethods(); + + remoteClassName = remoteClass.getName(); + stubClassName = Names.stubFor(remoteClassName); + skeletonClassName = Names.skeletonFor(remoteClassName); + + methodFieldNames = nameMethodFields(remoteMethods); + + stubFile = sourceFileForClass(remoteClassName,stubClassName, destDir , env); + skeletonFile = sourceFileForClass(remoteClassName,skeletonClassName, destDir, env); + + /* + * Initialize cached definitions for exception classes used + * in the generation process. + */ + defException = + env.getClassDeclaration(idJavaLangException). + getClassDefinition(env); + defRemoteException = + env.getClassDeclaration(idRemoteException). + getClassDefinition(env); + defRuntimeException = + env.getClassDeclaration(idJavaLangRuntimeException). + getClassDefinition(env); + } + + /** + * Write the stub for the remote class to a stream. + */ + private void writeStub(IndentingWriter p) throws IOException { + + /* + * Write boiler plate comment. + */ + p.pln("// Stub class generated by rmic, do not edit."); + p.pln("// Contents subject to change without notice."); + p.pln(); + + /* + * If remote implementation class was in a particular package, + * declare the stub class to be in the same package. + */ + if (remoteClassName.isQualified()) { + p.pln("package " + remoteClassName.getQualifier() + ";"); + p.pln(); + } + + /* + * Declare the stub class; implement all remote interfaces. + */ + p.plnI("public final class " + + Names.mangleClass(stubClassName.getName())); + p.pln("extends " + idRemoteStub); + ClassDefinition[] remoteInterfaces = remoteClass.getRemoteInterfaces(); + if (remoteInterfaces.length > 0) { + p.p("implements "); + for (int i = 0; i < remoteInterfaces.length; i++) { + if (i > 0) + p.p(", "); + p.p(remoteInterfaces[i].getName().toString()); + } + p.pln(); + } + p.pOlnI("{"); + + if (version == STUB_VERSION_1_1 || + version == STUB_VERSION_FAT) + { + writeOperationsArray(p); + p.pln(); + writeInterfaceHash(p); + p.pln(); + } + + if (version == STUB_VERSION_FAT || + version == STUB_VERSION_1_2) + { + p.pln("private static final long serialVersionUID = " + + STUB_SERIAL_VERSION_UID + ";"); + p.pln(); + + /* + * We only need to declare and initialize the static fields of + * Method objects for each remote method if there are any remote + * methods; otherwise, skip this code entirely, to avoid generating + * a try/catch block for a checked exception that cannot occur + * (see bugid 4125181). + */ + if (methodFieldNames.length > 0) { + if (version == STUB_VERSION_FAT) { + p.pln("private static boolean useNewInvoke;"); + } + writeMethodFieldDeclarations(p); + p.pln(); + + /* + * Initialize java.lang.reflect.Method fields for each remote + * method in a static initializer. + */ + p.plnI("static {"); + p.plnI("try {"); + if (version == STUB_VERSION_FAT) { + /* + * Fat stubs must determine whether the API required for + * the JDK 1.2 stub protocol is supported in the current + * runtime, so that it can use it if supported. This is + * determined by using the Reflection API to test if the + * new invoke method on RemoteRef exists, and setting the + * static boolean "useNewInvoke" to true if it does, or + * to false if a NoSuchMethodException is thrown. + */ + p.plnI(idRemoteRef + ".class.getMethod(\"invoke\","); + p.plnI("new java.lang.Class[] {"); + p.pln(idRemote + ".class,"); + p.pln("java.lang.reflect.Method.class,"); + p.pln("java.lang.Object[].class,"); + p.pln("long.class"); + p.pOln("});"); + p.pO(); + p.pln("useNewInvoke = true;"); + } + writeMethodFieldInitializers(p); + p.pOlnI("} catch (java.lang.NoSuchMethodException e) {"); + if (version == STUB_VERSION_FAT) { + p.pln("useNewInvoke = false;"); + } else { + /* + * REMIND: By throwing an Error here, the application will + * get the NoSuchMethodError directly when the stub class + * is initialized. If we throw a RuntimeException + * instead, the application would get an + * ExceptionInInitializerError. Would that be more + * appropriate, and if so, which RuntimeException should + * be thrown? + */ + p.plnI("throw new java.lang.NoSuchMethodError("); + p.pln("\"stub class initialization failed\");"); + p.pO(); + } + p.pOln("}"); // end try/catch block + p.pOln("}"); // end static initializer + p.pln(); + } + } + + writeStubConstructors(p); + p.pln(); + + /* + * Write each stub method. + */ + if (remoteMethods.length > 0) { + p.pln("// methods from remote interfaces"); + for (int i = 0; i < remoteMethods.length; ++i) { + p.pln(); + writeStubMethod(p, i); + } + } + + p.pOln("}"); // end stub class + } + + /** + * Write the constructors for the stub class. + */ + private void writeStubConstructors(IndentingWriter p) + throws IOException + { + p.pln("// constructors"); + + /* + * Only stubs compatible with the JDK 1.1 stub protocol need + * a no-arg constructor; later versions use reflection to find + * the constructor that directly takes a RemoteRef argument. + */ + if (version == STUB_VERSION_1_1 || + version == STUB_VERSION_FAT) + { + p.plnI("public " + Names.mangleClass(stubClassName.getName()) + + "() {"); + p.pln("super();"); + p.pOln("}"); + } + + p.plnI("public " + Names.mangleClass(stubClassName.getName()) + + "(" + idRemoteRef + " ref) {"); + p.pln("super(ref);"); + p.pOln("}"); + } + + /** + * Write the stub method for the remote method with the given "opnum". + */ + private void writeStubMethod(IndentingWriter p, int opnum) + throws IOException + { + RemoteClass.Method method = remoteMethods[opnum]; + Identifier methodName = method.getName(); + Type methodType = method.getType(); + Type paramTypes[] = methodType.getArgumentTypes(); + String paramNames[] = nameParameters(paramTypes); + Type returnType = methodType.getReturnType(); + ClassDeclaration[] exceptions = method.getExceptions(); + + /* + * Declare stub method; throw exceptions declared in remote + * interface(s). + */ + p.pln("// implementation of " + + methodType.typeString(methodName.toString(), true, false)); + p.p("public " + returnType + " " + methodName + "("); + for (int i = 0; i < paramTypes.length; i++) { + if (i > 0) + p.p(", "); + p.p(paramTypes[i] + " " + paramNames[i]); + } + p.plnI(")"); + if (exceptions.length > 0) { + p.p("throws "); + for (int i = 0; i < exceptions.length; i++) { + if (i > 0) + p.p(", "); + p.p(exceptions[i].getName().toString()); + } + p.pln(); + } + p.pOlnI("{"); + + /* + * The RemoteRef.invoke methods throw Exception, but unless this + * stub method throws Exception as well, we must catch Exceptions + * thrown from the invocation. So we must catch Exception and + * rethrow something we can throw: UnexpectedException, which is a + * subclass of RemoteException. But for any subclasses of Exception + * that we can throw, like RemoteException, RuntimeException, and + * any of the exceptions declared by this stub method, we want them + * to pass through unharmed, so first we must catch any such + * exceptions and rethrow it directly. + * + * We have to be careful generating the rethrowing catch blocks + * here, because javac will flag an error if there are any + * unreachable catch blocks, i.e. if the catch of an exception class + * follows a previous catch of it or of one of its superclasses. + * The following method invocation takes care of these details. + */ + Vector catchList = computeUniqueCatchList(exceptions); + + /* + * If we need to catch any particular exceptions (i.e. this method + * does not declare java.lang.Exception), put the entire stub + * method in a try block. + */ + if (catchList.size() > 0) { + p.plnI("try {"); + } + + if (version == STUB_VERSION_FAT) { + p.plnI("if (useNewInvoke) {"); + } + if (version == STUB_VERSION_FAT || + version == STUB_VERSION_1_2) + { + if (!returnType.isType(TC_VOID)) { + p.p("Object $result = "); // REMIND: why $? + } + p.p("ref.invoke(this, " + methodFieldNames[opnum] + ", "); + if (paramTypes.length > 0) { + p.p("new java.lang.Object[] {"); + for (int i = 0; i < paramTypes.length; i++) { + if (i > 0) + p.p(", "); + p.p(wrapArgumentCode(paramTypes[i], paramNames[i])); + } + p.p("}"); + } else { + p.p("null"); + } + p.pln(", " + method.getMethodHash() + "L);"); + if (!returnType.isType(TC_VOID)) { + p.pln("return " + + unwrapArgumentCode(returnType, "$result") + ";"); + } + } + if (version == STUB_VERSION_FAT) { + p.pOlnI("} else {"); + } + if (version == STUB_VERSION_1_1 || + version == STUB_VERSION_FAT) + { + p.pln(idRemoteCall + " call = ref.newCall((" + idRemoteObject + + ") this, operations, " + opnum + ", interfaceHash);"); + + if (paramTypes.length > 0) { + p.plnI("try {"); + p.pln("java.io.ObjectOutput out = call.getOutputStream();"); + writeMarshalArguments(p, "out", paramTypes, paramNames); + p.pOlnI("} catch (java.io.IOException e) {"); + p.pln("throw new " + idMarshalException + + "(\"error marshalling arguments\", e);"); + p.pOln("}"); + } + + p.pln("ref.invoke(call);"); + + if (returnType.isType(TC_VOID)) { + p.pln("ref.done(call);"); + } else { + p.pln(returnType + " $result;"); // REMIND: why $? + p.plnI("try {"); + p.pln("java.io.ObjectInput in = call.getInputStream();"); + boolean objectRead = + writeUnmarshalArgument(p, "in", returnType, "$result"); + p.pln(";"); + p.pOlnI("} catch (java.io.IOException e) {"); + p.pln("throw new " + idUnmarshalException + + "(\"error unmarshalling return\", e);"); + /* + * If any only if readObject has been invoked, we must catch + * ClassNotFoundException as well as IOException. + */ + if (objectRead) { + p.pOlnI("} catch (java.lang.ClassNotFoundException e) {"); + p.pln("throw new " + idUnmarshalException + + "(\"error unmarshalling return\", e);"); + } + p.pOlnI("} finally {"); + p.pln("ref.done(call);"); + p.pOln("}"); + p.pln("return $result;"); + } + } + if (version == STUB_VERSION_FAT) { + p.pOln("}"); // end if/else (useNewInvoke) block + } + + /* + * If we need to catch any particular exceptions, finally write + * the catch blocks for them, rethrow any other Exceptions with an + * UnexpectedException, and end the try block. + */ + if (catchList.size() > 0) { + for (Enumeration enumeration = catchList.elements(); + enumeration.hasMoreElements();) + { + ClassDefinition def = (ClassDefinition) enumeration.nextElement(); + p.pOlnI("} catch (" + def.getName() + " e) {"); + p.pln("throw e;"); + } + p.pOlnI("} catch (java.lang.Exception e) {"); + p.pln("throw new " + idUnexpectedException + + "(\"undeclared checked exception\", e);"); + p.pOln("}"); // end try/catch block + } + + p.pOln("}"); // end stub method + } + + /** + * Compute the exceptions which need to be caught and rethrown in a + * stub method before wrapping Exceptions in UnexpectedExceptions, + * given the exceptions declared in the throws clause of the method. + * Returns a Vector containing ClassDefinition objects for each + * exception to catch. Each exception is guaranteed to be unique, + * i.e. not a subclass of any of the other exceptions in the Vector, + * so the catch blocks for these exceptions may be generated in any + * order relative to each other. + * + * RemoteException and RuntimeException are each automatically placed + * in the returned Vector (if none of their superclasses are already + * present), since those exceptions should always be directly rethrown + * by a stub method. + * + * The returned Vector will be empty if java.lang.Exception or one + * of its superclasses is in the throws clause of the method, indicating + * that no exceptions need to be caught. + */ + private Vector computeUniqueCatchList(ClassDeclaration[] exceptions) { + Vector uniqueList = new Vector(); // unique exceptions to catch + + uniqueList.addElement(defRuntimeException); + uniqueList.addElement(defRemoteException); + + /* For each exception declared by the stub method's throws clause: */ + nextException: + for (int i = 0; i < exceptions.length; i++) { + ClassDeclaration decl = exceptions[i]; + try { + if (defException.subClassOf(env, decl)) { + /* + * (If java.lang.Exception (or a superclass) was declared + * in the throws clause of this stub method, then we don't + * have to bother catching anything; clear the list and + * return.) + */ + uniqueList.clear(); + break; + } else if (!defException.superClassOf(env, decl)) { + /* + * Ignore other Throwables that do not extend Exception, + * since they do not need to be caught anyway. + */ + continue; + } + /* + * Compare this exception against the current list of + * exceptions that need to be caught: + */ + for (int j = 0; j < uniqueList.size();) { + ClassDefinition def = + (ClassDefinition) uniqueList.elementAt(j); + if (def.superClassOf(env, decl)) { + /* + * If a superclass of this exception is already on + * the list to catch, then ignore and continue; + */ + continue nextException; + } else if (def.subClassOf(env, decl)) { + /* + * If a subclass of this exception is on the list + * to catch, then remove it. + */ + uniqueList.removeElementAt(j); + } else { + j++; // else continue comparing + } + } + /* This exception is unique: add it to the list to catch. */ + uniqueList.addElement(decl.getClassDefinition(env)); + } catch (ClassNotFound e) { + env.error(0, "class.not.found", e.name, decl.getName()); + /* + * REMIND: We do not exit from this exceptional condition, + * generating questionable code and likely letting the + * compiler report a resulting error later. + */ + } + } + return uniqueList; + } + + /** + * Write the skeleton for the remote class to a stream. + */ + private void writeSkeleton(IndentingWriter p) throws IOException { + if (version == STUB_VERSION_1_2) { + throw new Error("should not generate skeleton for version"); + } + + /* + * Write boiler plate comment. + */ + p.pln("// Skeleton class generated by rmic, do not edit."); + p.pln("// Contents subject to change without notice."); + p.pln(); + + /* + * If remote implementation class was in a particular package, + * declare the skeleton class to be in the same package. + */ + if (remoteClassName.isQualified()) { + p.pln("package " + remoteClassName.getQualifier() + ";"); + p.pln(); + } + + /* + * Declare the skeleton class. + */ + p.plnI("public final class " + + Names.mangleClass(skeletonClassName.getName())); + p.pln("implements " + idSkeleton); + p.pOlnI("{"); + + writeOperationsArray(p); + p.pln(); + + writeInterfaceHash(p); + p.pln(); + + /* + * Define the getOperations() method. + */ + p.plnI("public " + idOperation + "[] getOperations() {"); + p.pln("return (" + idOperation + "[]) operations.clone();"); + p.pOln("}"); + p.pln(); + + /* + * Define the dispatch() method. + */ + p.plnI("public void dispatch(" + idRemote + " obj, " + + idRemoteCall + " call, int opnum, long hash)"); + p.pln("throws java.lang.Exception"); + p.pOlnI("{"); + + if (version == STUB_VERSION_FAT) { + p.plnI("if (opnum < 0) {"); + if (remoteMethods.length > 0) { + for (int opnum = 0; opnum < remoteMethods.length; opnum++) { + if (opnum > 0) + p.pO("} else "); + p.plnI("if (hash == " + + remoteMethods[opnum].getMethodHash() + "L) {"); + p.pln("opnum = " + opnum + ";"); + } + p.pOlnI("} else {"); + } + /* + * Skeleton throws UnmarshalException if it does not recognize + * the method hash; this is what UnicastServerRef.dispatch() + * would do. + */ + p.pln("throw new " + + idUnmarshalException + "(\"invalid method hash\");"); + if (remoteMethods.length > 0) { + p.pOln("}"); + } + /* + * Ignore the validation of the interface hash if the + * operation number was negative, since it is really a + * method hash instead. + */ + p.pOlnI("} else {"); + } + + p.plnI("if (hash != interfaceHash)"); + p.pln("throw new " + + idSkeletonMismatchException + "(\"interface hash mismatch\");"); + p.pO(); + + if (version == STUB_VERSION_FAT) { + p.pOln("}"); // end if/else (opnum < 0) block + } + p.pln(); + + /* + * Cast remote object instance to our specific implementation class. + */ + p.pln(remoteClassName + " server = (" + remoteClassName + ") obj;"); + + /* + * Process call according to the operation number. + */ + p.plnI("switch (opnum) {"); + for (int opnum = 0; opnum < remoteMethods.length; opnum++) { + writeSkeletonDispatchCase(p, opnum); + } + p.pOlnI("default:"); + /* + * Skeleton throws UnmarshalException if it does not recognize + * the operation number; this is consistent with the case of an + * unrecognized method hash. + */ + p.pln("throw new " + idUnmarshalException + + "(\"invalid method number\");"); + p.pOln("}"); // end switch statement + + p.pOln("}"); // end dispatch() method + + p.pOln("}"); // end skeleton class + } + + /** + * Write the case block for the skeleton's dispatch method for + * the remote method with the given "opnum". + */ + private void writeSkeletonDispatchCase(IndentingWriter p, int opnum) + throws IOException + { + RemoteClass.Method method = remoteMethods[opnum]; + Identifier methodName = method.getName(); + Type methodType = method.getType(); + Type paramTypes[] = methodType.getArgumentTypes(); + String paramNames[] = nameParameters(paramTypes); + Type returnType = methodType.getReturnType(); + + p.pOlnI("case " + opnum + ": // " + + methodType.typeString(methodName.toString(), true, false)); + /* + * Use nested block statement inside case to provide an independent + * namespace for local variables used to unmarshal parameters for + * this remote method. + */ + p.pOlnI("{"); + + if (paramTypes.length > 0) { + /* + * Declare local variables to hold arguments. + */ + for (int i = 0; i < paramTypes.length; i++) { + p.pln(paramTypes[i] + " " + paramNames[i] + ";"); + } + + /* + * Unmarshal arguments from call stream. + */ + p.plnI("try {"); + p.pln("java.io.ObjectInput in = call.getInputStream();"); + boolean objectsRead = writeUnmarshalArguments(p, "in", + paramTypes, paramNames); + p.pOlnI("} catch (java.io.IOException e) {"); + p.pln("throw new " + idUnmarshalException + + "(\"error unmarshalling arguments\", e);"); + /* + * If any only if readObject has been invoked, we must catch + * ClassNotFoundException as well as IOException. + */ + if (objectsRead) { + p.pOlnI("} catch (java.lang.ClassNotFoundException e) {"); + p.pln("throw new " + idUnmarshalException + + "(\"error unmarshalling arguments\", e);"); + } + p.pOlnI("} finally {"); + p.pln("call.releaseInputStream();"); + p.pOln("}"); + } else { + p.pln("call.releaseInputStream();"); + } + + if (!returnType.isType(TC_VOID)) { + /* + * Declare variable to hold return type, if not void. + */ + p.p(returnType + " $result = "); // REMIND: why $? + } + + /* + * Invoke the method on the server object. + */ + p.p("server." + methodName + "("); + for (int i = 0; i < paramNames.length; i++) { + if (i > 0) + p.p(", "); + p.p(paramNames[i]); + } + p.pln(");"); + + /* + * Always invoke getResultStream(true) on the call object to send + * the indication of a successful invocation to the caller. If + * the return type is not void, keep the result stream and marshal + * the return value. + */ + p.plnI("try {"); + if (!returnType.isType(TC_VOID)) { + p.p("java.io.ObjectOutput out = "); + } + p.pln("call.getResultStream(true);"); + if (!returnType.isType(TC_VOID)) { + writeMarshalArgument(p, "out", returnType, "$result"); + p.pln(";"); + } + p.pOlnI("} catch (java.io.IOException e) {"); + p.pln("throw new " + + idMarshalException + "(\"error marshalling return\", e);"); + p.pOln("}"); + + p.pln("break;"); // break from switch statement + + p.pOlnI("}"); // end nested block statement + p.pln(); + } + + /** + * Write declaration and initializer for "operations" static array. + */ + private void writeOperationsArray(IndentingWriter p) + throws IOException + { + p.plnI("private static final " + idOperation + "[] operations = {"); + for (int i = 0; i < remoteMethods.length; i++) { + if (i > 0) + p.pln(","); + p.p("new " + idOperation + "(\"" + + remoteMethods[i].getOperationString() + "\")"); + } + p.pln(); + p.pOln("};"); + } + + /** + * Write declaration and initializer for "interfaceHash" static field. + */ + private void writeInterfaceHash(IndentingWriter p) + throws IOException + { + p.pln("private static final long interfaceHash = " + + remoteClass.getInterfaceHash() + "L;"); + } + + /** + * Write declaration for java.lang.reflect.Method static fields + * corresponding to each remote method in a stub. + */ + private void writeMethodFieldDeclarations(IndentingWriter p) + throws IOException + { + for (int i = 0; i < methodFieldNames.length; i++) { + p.pln("private static java.lang.reflect.Method " + + methodFieldNames[i] + ";"); + } + } + + /** + * Write code to initialize the static fields for each method + * using the Java Reflection API. + */ + private void writeMethodFieldInitializers(IndentingWriter p) + throws IOException + { + for (int i = 0; i < methodFieldNames.length; i++) { + p.p(methodFieldNames[i] + " = "); + /* + * Here we look up the Method object in the arbitrary interface + * that we find in the RemoteClass.Method object. + * REMIND: Is this arbitrary choice OK? + * REMIND: Should this access be part of RemoteClass.Method's + * abstraction? + */ + RemoteClass.Method method = remoteMethods[i]; + MemberDefinition def = method.getMemberDefinition(); + Identifier methodName = method.getName(); + Type methodType = method.getType(); + Type paramTypes[] = methodType.getArgumentTypes(); + + p.p(def.getClassDefinition().getName() + ".class.getMethod(\"" + + methodName + "\", new java.lang.Class[] {"); + for (int j = 0; j < paramTypes.length; j++) { + if (j > 0) + p.p(", "); + p.p(paramTypes[j] + ".class"); + } + p.pln("});"); + } + } + + + /* + * Following are a series of static utility methods useful during + * the code generation process: + */ + + /** + * Generate an array of names for fields that correspond to the given + * array of remote methods. Each name in the returned array is + * guaranteed to be unique. + * + * The name of a method is included in its corresponding field name + * to enhance readability of the generated code. + */ + private static String[] nameMethodFields(RemoteClass.Method[] methods) { + String[] names = new String[methods.length]; + for (int i = 0; i < names.length; i++) { + names[i] = "$method_" + methods[i].getName() + "_" + i; + } + return names; + } + + /** + * Generate an array of names for parameters corresponding to the + * given array of types for the parameters. Each name in the returned + * array is guaranteed to be unique. + * + * A representation of the type of a parameter is included in its + * corresponding field name to enhance the readability of the generated + * code. + */ + private static String[] nameParameters(Type[] types) { + String[] names = new String[types.length]; + for (int i = 0; i < names.length; i++) { + names[i] = "$param_" + + generateNameFromType(types[i]) + "_" + (i + 1); + } + return names; + } + + /** + * Generate a readable string representing the given type suitable + * for embedding within a Java identifier. + */ + private static String generateNameFromType(Type type) { + int typeCode = type.getTypeCode(); + switch (typeCode) { + case TC_BOOLEAN: + case TC_BYTE: + case TC_CHAR: + case TC_SHORT: + case TC_INT: + case TC_LONG: + case TC_FLOAT: + case TC_DOUBLE: + return type.toString(); + case TC_ARRAY: + return "arrayOf_" + generateNameFromType(type.getElementType()); + case TC_CLASS: + return Names.mangleClass(type.getClassName().getName()).toString(); + default: + throw new Error("unexpected type code: " + typeCode); + } + } + + /** + * Write a snippet of Java code to marshal a value named "name" of + * type "type" to the java.io.ObjectOutput stream named "stream". + * + * Primitive types are marshalled with their corresponding methods + * in the java.io.DataOutput interface, and objects (including arrays) + * are marshalled using the writeObject method. + */ + private static void writeMarshalArgument(IndentingWriter p, + String streamName, + Type type, String name) + throws IOException + { + int typeCode = type.getTypeCode(); + switch (typeCode) { + case TC_BOOLEAN: + p.p(streamName + ".writeBoolean(" + name + ")"); + break; + case TC_BYTE: + p.p(streamName + ".writeByte(" + name + ")"); + break; + case TC_CHAR: + p.p(streamName + ".writeChar(" + name + ")"); + break; + case TC_SHORT: + p.p(streamName + ".writeShort(" + name + ")"); + break; + case TC_INT: + p.p(streamName + ".writeInt(" + name + ")"); + break; + case TC_LONG: + p.p(streamName + ".writeLong(" + name + ")"); + break; + case TC_FLOAT: + p.p(streamName + ".writeFloat(" + name + ")"); + break; + case TC_DOUBLE: + p.p(streamName + ".writeDouble(" + name + ")"); + break; + case TC_ARRAY: + case TC_CLASS: + p.p(streamName + ".writeObject(" + name + ")"); + break; + default: + throw new Error("unexpected type code: " + typeCode); + } + } + + /** + * Write Java statements to marshal a series of values in order as + * named in the "names" array, with types as specified in the "types" + * array", to the java.io.ObjectOutput stream named "stream". + */ + private static void writeMarshalArguments(IndentingWriter p, + String streamName, + Type[] types, String[] names) + throws IOException + { + if (types.length != names.length) { + throw new Error("paramter type and name arrays different sizes"); + } + + for (int i = 0; i < types.length; i++) { + writeMarshalArgument(p, streamName, types[i], names[i]); + p.pln(";"); + } + } + + /** + * Write a snippet of Java code to unmarshal a value of type "type" + * from the java.io.ObjectInput stream named "stream" into a variable + * named "name" (if "name" is null, the value in unmarshalled and + * discarded). + * + * Primitive types are unmarshalled with their corresponding methods + * in the java.io.DataInput interface, and objects (including arrays) + * are unmarshalled using the readObject method. + */ + private static boolean writeUnmarshalArgument(IndentingWriter p, + String streamName, + Type type, String name) + throws IOException + { + boolean readObject = false; + + if (name != null) { + p.p(name + " = "); + } + + int typeCode = type.getTypeCode(); + switch (type.getTypeCode()) { + case TC_BOOLEAN: + p.p(streamName + ".readBoolean()"); + break; + case TC_BYTE: + p.p(streamName + ".readByte()"); + break; + case TC_CHAR: + p.p(streamName + ".readChar()"); + break; + case TC_SHORT: + p.p(streamName + ".readShort()"); + break; + case TC_INT: + p.p(streamName + ".readInt()"); + break; + case TC_LONG: + p.p(streamName + ".readLong()"); + break; + case TC_FLOAT: + p.p(streamName + ".readFloat()"); + break; + case TC_DOUBLE: + p.p(streamName + ".readDouble()"); + break; + case TC_ARRAY: + case TC_CLASS: + p.p("(" + type + ") " + streamName + ".readObject()"); + readObject = true; + break; + default: + throw new Error("unexpected type code: " + typeCode); + } + return readObject; + } + + /** + * Write Java statements to unmarshal a series of values in order of + * types as in the "types" array from the java.io.ObjectInput stream + * named "stream" into variables as named in "names" (for any element + * of "names" that is null, the corresponding value is unmarshalled + * and discarded). + */ + private static boolean writeUnmarshalArguments(IndentingWriter p, + String streamName, + Type[] types, + String[] names) + throws IOException + { + if (types.length != names.length) { + throw new Error("paramter type and name arrays different sizes"); + } + + boolean readObject = false; + for (int i = 0; i < types.length; i++) { + if (writeUnmarshalArgument(p, streamName, types[i], names[i])) { + readObject = true; + } + p.pln(";"); + } + return readObject; + } + + /** + * Return a snippet of Java code to wrap a value named "name" of + * type "type" into an object as appropriate for use by the + * Java Reflection API. + * + * For primitive types, an appropriate wrapper class instantiated + * with the primitive value. For object types (including arrays), + * no wrapping is necessary, so the value is named directly. + */ + private static String wrapArgumentCode(Type type, String name) { + int typeCode = type.getTypeCode(); + switch (typeCode) { + case TC_BOOLEAN: + return ("(" + name + + " ? java.lang.Boolean.TRUE : java.lang.Boolean.FALSE)"); + case TC_BYTE: + return "new java.lang.Byte(" + name + ")"; + case TC_CHAR: + return "new java.lang.Character(" + name + ")"; + case TC_SHORT: + return "new java.lang.Short(" + name + ")"; + case TC_INT: + return "new java.lang.Integer(" + name + ")"; + case TC_LONG: + return "new java.lang.Long(" + name + ")"; + case TC_FLOAT: + return "new java.lang.Float(" + name + ")"; + case TC_DOUBLE: + return "new java.lang.Double(" + name + ")"; + case TC_ARRAY: + case TC_CLASS: + return name; + default: + throw new Error("unexpected type code: " + typeCode); + } + } + + /** + * Return a snippet of Java code to unwrap a value named "name" into + * a value of type "type", as appropriate for the Java Reflection API. + * + * For primitive types, the value is assumed to be of the corresponding + * wrapper type, and a method is called on the wrapper type to retrieve + * the primitive value. For object types (include arrays), no + * unwrapping is necessary; the value is simply cast to the expected + * real object type. + */ + private static String unwrapArgumentCode(Type type, String name) { + int typeCode = type.getTypeCode(); + switch (typeCode) { + case TC_BOOLEAN: + return "((java.lang.Boolean) " + name + ").booleanValue()"; + case TC_BYTE: + return "((java.lang.Byte) " + name + ").byteValue()"; + case TC_CHAR: + return "((java.lang.Character) " + name + ").charValue()"; + case TC_SHORT: + return "((java.lang.Short) " + name + ").shortValue()"; + case TC_INT: + return "((java.lang.Integer) " + name + ").intValue()"; + case TC_LONG: + return "((java.lang.Long) " + name + ").longValue()"; + case TC_FLOAT: + return "((java.lang.Float) " + name + ").floatValue()"; + case TC_DOUBLE: + return "((java.lang.Double) " + name + ").doubleValue()"; + case TC_ARRAY: + case TC_CLASS: + return "((" + type + ") " + name + ")"; + default: + throw new Error("unexpected type code: " + typeCode); + } + } +} diff --git a/src/share/classes/sun/rmi/rmic/RemoteClass.java b/src/share/classes/sun/rmi/rmic/RemoteClass.java new file mode 100644 index 000000000..0caa07fe0 --- /dev/null +++ b/src/share/classes/sun/rmi/rmic/RemoteClass.java @@ -0,0 +1,875 @@ +/* + * Copyright 1997-2004 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.rmic; + +import java.util.Vector; +import java.util.Hashtable; +import java.util.Enumeration; +import java.io.IOException; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.security.MessageDigest; +import java.security.DigestOutputStream; +import java.security.NoSuchAlgorithmException; +import sun.tools.java.Type; +import sun.tools.java.ClassDefinition; +import sun.tools.java.ClassDeclaration; +import sun.tools.java.MemberDefinition; +import sun.tools.java.Identifier; +import sun.tools.java.ClassNotFound; + +/** + * A RemoteClass object encapsulates RMI-specific information about + * a remote implementation class, i.e. a class that implements + * one or more remote interfaces. + * + * WARNING: The contents of this source file are not part of any + * supported API. Code that depends on them does so at its own risk: + * they are subject to change or removal without notice. + * + * @author Peter Jones + */ +public class RemoteClass implements sun.rmi.rmic.RMIConstants { + + /** + * Create a RemoteClass object representing the remote meta-information + * of the given class. + * + * Returns true if successful. If the class is not a properly formed + * remote implementation class or if some other error occurs, the + * return value will be null, and errors will have been reported to + * the supplied BatchEnvironment. + */ + public static RemoteClass forClass(BatchEnvironment env, + ClassDefinition implClassDef) + { + RemoteClass rc = new RemoteClass(env, implClassDef); + if (rc.initialize()) { + return rc; + } else { + return null; + } + } + + /** + * Return the ClassDefinition for this class. + */ + public ClassDefinition getClassDefinition() { + return implClassDef; + } + + /** + * Return the name of the class represented by this object. + */ + public Identifier getName() { + return implClassDef.getName(); + } + + /** + * Return an array of ClassDefinitions representing all of the remote + * interfaces implemented by this class. + * + * A remote interface is any interface that extends Remote, + * directly or indirectly. The remote interfaces of a class + * are the interfaces directly listed in either the class's + * "implements" clause, or the "implements" clause of any + * of its superclasses, that are remote interfaces. + * + * The order of the array returned is arbitrary, and some elements + * may be superfluous (i.e., superinterfaces of other interfaces + * in the array). + */ + public ClassDefinition[] getRemoteInterfaces() { + return (ClassDefinition[]) remoteInterfaces.clone(); + } + + /** + * Return an array of RemoteClass.Method objects representing all of + * the remote methods implemented by this class, i.e. all of the + * methods in the class's remote interfaces. + * + * The methods in the array are ordered according to the comparision + * of the strings consisting of their method name followed by their + * type signature, so each method's index in the array corresponds + * to its "operation number" in the JDK 1.1 version of the + * stub/skeleton protocol. + */ + public Method[] getRemoteMethods() { + return (Method[]) remoteMethods.clone(); + } + + /** + * Return the "interface hash" used to match a stub/skeleton pair for + * this class in the JDK 1.1 version of the stub/skeleton protocol. + */ + public long getInterfaceHash() { + return interfaceHash; + } + + /** + * Return string representation of this object, consisting of + * the string "remote class " followed by the class name. + */ + public String toString() { + return "remote class " + implClassDef.getName().toString(); + } + + /** rmic environment for this object */ + private BatchEnvironment env; + + /** the remote implementation class this object corresponds to */ + private ClassDefinition implClassDef; + + /** remote interfaces implemented by this class */ + private ClassDefinition[] remoteInterfaces; + + /** all the remote methods of this class */ + private Method[] remoteMethods; + + /** stub/skeleton "interface hash" for this class */ + private long interfaceHash; + + /** cached definition for certain classes used in this environment */ + private ClassDefinition defRemote; + private ClassDefinition defException; + private ClassDefinition defRemoteException; + + /** + * Create a RemoteClass instance for the given class. The resulting + * object is not yet initialized. + */ + private RemoteClass(BatchEnvironment env, ClassDefinition implClassDef) { + this.env = env; + this.implClassDef = implClassDef; + } + + /** + * Validate that the remote implementation class is properly formed + * and fill in the data structures required by the public interface. + */ + private boolean initialize() { + /* + * Verify that the "impl" is really a class, not an interface. + */ + if (implClassDef.isInterface()) { + env.error(0, "rmic.cant.make.stubs.for.interface", + implClassDef.getName()); + return false; + } + + /* + * Initialize cached definitions for the Remote interface and + * the RemoteException class. + */ + try { + defRemote = + env.getClassDeclaration(idRemote).getClassDefinition(env); + defException = + env.getClassDeclaration(idJavaLangException). + getClassDefinition(env); + defRemoteException = + env.getClassDeclaration(idRemoteException). + getClassDefinition(env); + } catch (ClassNotFound e) { + env.error(0, "rmic.class.not.found", e.name); + return false; + } + + /* + * Here we find all of the remote interfaces of our remote + * implementation class. For each class up the superclass + * chain, add each directly-implemented interface that + * somehow extends Remote to a list. + */ + Vector remotesImplemented = // list of remote interfaces found + new Vector(); + for (ClassDefinition classDef = implClassDef; + classDef != null;) + { + try { + ClassDeclaration[] interfaces = classDef.getInterfaces(); + for (int i = 0; i < interfaces.length; i++) { + ClassDefinition interfaceDef = + interfaces[i].getClassDefinition(env); + /* + * Add interface to the list if it extends Remote and + * it is not already there. + */ + if (!remotesImplemented.contains(interfaceDef) && + defRemote.implementedBy(env, interfaces[i])) + { + remotesImplemented.addElement(interfaceDef); + /***** <DEBUG> */ + if (env.verbose()) { + System.out.println("[found remote interface: " + + interfaceDef.getName() + "]"); + /***** </DEBUG> */ + } + } + } + + /* + * Verify that the candidate remote implementation class + * implements at least one remote interface directly. + */ + if (classDef == implClassDef && remotesImplemented.isEmpty()) { + if (defRemote.implementedBy(env, + implClassDef.getClassDeclaration())) + { + /* + * This error message is used if the class does + * implement a remote interface through one of + * its superclasses, but not directly. + */ + env.error(0, "rmic.must.implement.remote.directly", + implClassDef.getName()); + } else { + /* + * This error message is used if the class never + * implements a remote interface. + */ + env.error(0, "rmic.must.implement.remote", + implClassDef.getName()); + } + return false; + } + + /* + * Get definition for next superclass. + */ + classDef = (classDef.getSuperClass() != null ? + classDef.getSuperClass().getClassDefinition(env) : + null); + + } catch (ClassNotFound e) { + env.error(0, "class.not.found", e.name, classDef.getName()); + return false; + } + } + + /* + * The "remotesImplemented" vector now contains all of the remote + * interfaces directly implemented by the remote class or by any + * of its superclasses. + * + * At this point, we could optimize the list by removing superfluous + * entries, i.e. any interfaces that are implemented by some other + * interface in the list anyway. + * + * This should be correct; would it be worthwhile? + * + * for (int i = 0; i < remotesImplemented.size();) { + * ClassDefinition interfaceDef = + * (ClassDefinition) remotesImplemented.elementAt(i); + * boolean isOtherwiseImplemented = false; + * for (int j = 0; j < remotesImplemented.size; j++) { + * if (j != i && + * interfaceDef.implementedBy(env, (ClassDefinition) + * remotesImplemented.elementAt(j). + * getClassDeclaration())) + * { + * isOtherwiseImplemented = true; + * break; + * } + * } + * if (isOtherwiseImplemented) { + * remotesImplemented.removeElementAt(i); + * } else { + * ++i; + * } + * } + */ + + /* + * Now we collect the methods from all of the remote interfaces + * into a hashtable. + */ + Hashtable methods = new Hashtable(); + boolean errors = false; + for (Enumeration enumeration = remotesImplemented.elements(); + enumeration.hasMoreElements();) + { + ClassDefinition interfaceDef = + (ClassDefinition) enumeration.nextElement(); + if (!collectRemoteMethods(interfaceDef, methods)) + errors = true; + } + if (errors) + return false; + + /* + * Convert vector of remote interfaces to an array + * (order is not important for this array). + */ + remoteInterfaces = new ClassDefinition[remotesImplemented.size()]; + remotesImplemented.copyInto(remoteInterfaces); + + /* + * Sort table of remote methods into an array. The elements are + * sorted in ascending order of the string of the method's name + * and type signature, so that each elements index is equal to + * its operation number of the JDK 1.1 version of the stub/skeleton + * protocol. + */ + String[] orderedKeys = new String[methods.size()]; + int count = 0; + for (Enumeration enumeration = methods.elements(); + enumeration.hasMoreElements();) + { + Method m = (Method) enumeration.nextElement(); + String key = m.getNameAndDescriptor(); + int i; + for (i = count; i > 0; --i) { + if (key.compareTo(orderedKeys[i - 1]) >= 0) { + break; + } + orderedKeys[i] = orderedKeys[i - 1]; + } + orderedKeys[i] = key; + ++count; + } + remoteMethods = new Method[methods.size()]; + for (int i = 0; i < remoteMethods.length; i++) { + remoteMethods[i] = (Method) methods.get(orderedKeys[i]); + /***** <DEBUG> */ + if (env.verbose()) { + System.out.print("[found remote method <" + i + ">: " + + remoteMethods[i].getOperationString()); + ClassDeclaration[] exceptions = + remoteMethods[i].getExceptions(); + if (exceptions.length > 0) + System.out.print(" throws "); + for (int j = 0; j < exceptions.length; j++) { + if (j > 0) + System.out.print(", "); + System.out.print(exceptions[j].getName()); + } + System.out.println("]"); + } + /***** </DEBUG> */ + } + + /** + * Finally, pre-compute the interface hash to be used by + * stubs/skeletons for this remote class. + */ + interfaceHash = computeInterfaceHash(); + + return true; + } + + /** + * Collect and validate all methods from given interface and all of + * its superinterfaces as remote methods. Remote methods are added + * to the supplied hashtable. Returns true if successful, + * or false if an error occurred. + */ + private boolean collectRemoteMethods(ClassDefinition interfaceDef, + Hashtable table) + { + if (!interfaceDef.isInterface()) { + throw new Error( + "expected interface, not class: " + interfaceDef.getName()); + } + + /* + * rmic used to enforce that a remote interface could not extend + * a non-remote interface, i.e. an interface that did not itself + * extend from Remote. The current version of rmic does not have + * this restriction, so the following code is now commented out. + * + * Verify that this interface extends Remote, since all interfaces + * extended by a remote interface must implement Remote. + * + * try { + * if (!defRemote.implementedBy(env, + * interfaceDef.getClassDeclaration())) + * { + * env.error(0, "rmic.can.mix.remote.nonremote", + * interfaceDef.getName()); + * return false; + * } + * } catch (ClassNotFound e) { + * env.error(0, "class.not.found", e.name, + * interfaceDef.getName()); + * return false; + * } + */ + + boolean errors = false; + + /* + * Search interface's members for methods. + */ + nextMember: + for (MemberDefinition member = interfaceDef.getFirstMember(); + member != null; + member = member.getNextMember()) + { + if (member.isMethod() && + !member.isConstructor() && !member.isInitializer()) + { + /* + * Verify that each method throws RemoteException. + */ + ClassDeclaration[] exceptions = member.getExceptions(env); + boolean hasRemoteException = false; + for (int i = 0; i < exceptions.length; i++) { + /* + * rmic used to enforce that a remote method had to + * explicitly list RemoteException in its "throws" + * clause; i.e., just throwing Exception was not + * acceptable. The current version of rmic does not + * have this restriction, so the following code is + * now commented out. Instead, the method is + * considered valid if RemoteException is a subclass + * of any of the methods declared exceptions. + * + * if (exceptions[i].getName().equals( + * idRemoteException)) + * { + * hasRemoteException = true; + * break; + * } + */ + try { + if (defRemoteException.subClassOf( + env, exceptions[i])) + { + hasRemoteException = true; + break; + } + } catch (ClassNotFound e) { + env.error(0, "class.not.found", e.name, + interfaceDef.getName()); + continue nextMember; + } + } + /* + * If this method did not throw RemoteException as required, + * generate the error but continue, so that multiple such + * errors can be reported. + */ + if (!hasRemoteException) { + env.error(0, "rmic.must.throw.remoteexception", + interfaceDef.getName(), member.toString()); + errors = true; + continue nextMember; + } + + /* + * Verify that the implementation of this method throws only + * java.lang.Exception or its subclasses (fix bugid 4092486). + * JRMP does not support remote methods throwing + * java.lang.Throwable or other subclasses. + */ + try { + MemberDefinition implMethod = implClassDef.findMethod( + env, member.getName(), member.getType()); + if (implMethod != null) { // should not be null + exceptions = implMethod.getExceptions(env); + for (int i = 0; i < exceptions.length; i++) { + if (!defException.superClassOf( + env, exceptions[i])) + { + env.error(0, "rmic.must.only.throw.exception", + implMethod.toString(), + exceptions[i].getName()); + errors = true; + continue nextMember; + } + } + } + } catch (ClassNotFound e) { + env.error(0, "class.not.found", e.name, + implClassDef.getName()); + continue nextMember; + } + + /* + * Create RemoteClass.Method object to represent this method + * found in a remote interface. + */ + Method newMethod = new Method(member); + /* + * Store remote method's representation in the table of + * remote methods found, keyed by its name and parameter + * signature. + * + * If the table already contains an entry with the same + * method name and parameter signature, then we must + * replace the old entry with a Method object that + * represents a legal combination of the old and the new + * methods; specifically, the combined method must have + * a throws list that contains (only) all of the checked + * exceptions that can be thrown by both the old or + * the new method (see bugid 4070653). + */ + String key = newMethod.getNameAndDescriptor(); + Method oldMethod = (Method) table.get(key); + if (oldMethod != null) { + newMethod = newMethod.mergeWith(oldMethod); + if (newMethod == null) { + errors = true; + continue nextMember; + } + } + table.put(key, newMethod); + } + } + + /* + * Recursively collect methods for all superinterfaces. + */ + try { + ClassDeclaration[] superDefs = interfaceDef.getInterfaces(); + for (int i = 0; i < superDefs.length; i++) { + ClassDefinition superDef = + superDefs[i].getClassDefinition(env); + if (!collectRemoteMethods(superDef, table)) + errors = true; + } + } catch (ClassNotFound e) { + env.error(0, "class.not.found", e.name, interfaceDef.getName()); + return false; + } + + return !errors; + } + + /** + * Compute the "interface hash" of the stub/skeleton pair for this + * remote implementation class. This is the 64-bit value used to + * enforce compatibility between a stub and a skeleton using the + * JDK 1.1 version of the stub/skeleton protocol. + * + * It is calculated using the first 64 bits of a SHA digest. The + * digest is from a stream consisting of the following data: + * (int) stub version number, always 1 + * for each remote method, in order of operation number: + * (UTF) method name + * (UTF) method type signature + * for each declared exception, in alphabetical name order: + * (UTF) name of exception class + * + */ + private long computeInterfaceHash() { + long hash = 0; + ByteArrayOutputStream sink = new ByteArrayOutputStream(512); + try { + MessageDigest md = MessageDigest.getInstance("SHA"); + DataOutputStream out = new DataOutputStream( + new DigestOutputStream(sink, md)); + + out.writeInt(INTERFACE_HASH_STUB_VERSION); + for (int i = 0; i < remoteMethods.length; i++) { + MemberDefinition m = remoteMethods[i].getMemberDefinition(); + Identifier name = m.getName(); + Type type = m.getType(); + + out.writeUTF(name.toString()); + // type signatures already use mangled class names + out.writeUTF(type.getTypeSignature()); + + ClassDeclaration exceptions[] = m.getExceptions(env); + sortClassDeclarations(exceptions); + for (int j = 0; j < exceptions.length; j++) { + out.writeUTF(Names.mangleClass( + exceptions[j].getName()).toString()); + } + } + out.flush(); + + // use only the first 64 bits of the digest for the hash + byte hashArray[] = md.digest(); + for (int i = 0; i < Math.min(8, hashArray.length); i++) { + hash += ((long) (hashArray[i] & 0xFF)) << (i * 8); + } + } catch (IOException e) { + throw new Error( + "unexpected exception computing intetrface hash: " + e); + } catch (NoSuchAlgorithmException e) { + throw new Error( + "unexpected exception computing intetrface hash: " + e); + } + + return hash; + } + + /** + * Sort array of class declarations alphabetically by their mangled + * fully-qualfied class name. This is used to feed a method's exceptions + * in a canonical order into the digest stream for the interface hash + * computation. + */ + private void sortClassDeclarations(ClassDeclaration[] decl) { + for (int i = 1; i < decl.length; i++) { + ClassDeclaration curr = decl[i]; + String name = Names.mangleClass(curr.getName()).toString(); + int j; + for (j = i; j > 0; j--) { + if (name.compareTo( + Names.mangleClass(decl[j - 1].getName()).toString()) >= 0) + { + break; + } + decl[j] = decl[j - 1]; + } + decl[j] = curr; + } + } + + + /** + * A RemoteClass.Method object encapsulates RMI-specific information + * about a particular remote method in the remote implementation class + * represented by the outer instance. + */ + public class Method implements Cloneable { + + /** + * Return the definition of the actual class member corresponing + * to this method of a remote interface. + * + * REMIND: Can this method be removed? + */ + public MemberDefinition getMemberDefinition() { + return memberDef; + } + + /** + * Return the name of this method. + */ + public Identifier getName() { + return memberDef.getName(); + } + + /** + * Return the type of this method. + */ + public Type getType() { + return memberDef.getType(); + } + + /** + * Return an array of the exception classes declared to be + * thrown by this remote method. + * + * For methods with the same name and type signature inherited + * from multiple remote interfaces, the array will contain + * the set of exceptions declared in all of the interfaces' + * methods that can be legally thrown in each of them. + */ + public ClassDeclaration[] getExceptions() { + return (ClassDeclaration[]) exceptions.clone(); + } + + /** + * Return the "method hash" used to identify this remote method + * in the JDK 1.2 version of the stub protocol. + */ + public long getMethodHash() { + return methodHash; + } + + /** + * Return the string representation of this method. + */ + public String toString() { + return memberDef.toString(); + } + + /** + * Return the string representation of this method appropriate + * for the construction of a java.rmi.server.Operation object. + */ + public String getOperationString() { + return memberDef.toString(); + } + + /** + * Return a string consisting of this method's name followed by + * its method descriptor, using the Java VM's notation for + * method descriptors (see section 4.3.3 of The Java Virtual + * Machine Specification). + */ + public String getNameAndDescriptor() { + return memberDef.getName().toString() + + memberDef.getType().getTypeSignature(); + } + + /** + * Member definition for this method, from one of the remote + * interfaces that this method was found in. + * + * Note that this member definition may be only one of several + * member defintions that correspond to this remote method object, + * if several of this class's remote interfaces contain methods + * with the same name and type signature. Therefore, this member + * definition may declare more exceptions thrown that this remote + * method does. + */ + private MemberDefinition memberDef; + + /** stub "method hash" to identify this method */ + private long methodHash; + + /** + * Exceptions declared to be thrown by this remote method. + * + * This list can include superfluous entries, such as + * unchecked exceptions and subclasses of other entries. + */ + private ClassDeclaration[] exceptions; + + /** + * Create a new Method object corresponding to the given + * method definition. + */ + /* + * Temporarily comment out the private modifier until + * the VM allows outer class to access inner class's + * private constructor + */ + /* private */ Method(MemberDefinition memberDef) { + this.memberDef = memberDef; + exceptions = memberDef.getExceptions(env); + methodHash = computeMethodHash(); + } + + /** + * Cloning is supported by returning a shallow copy of this object. + */ + protected Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + throw new Error("clone failed"); + } + } + + /** + * Return a new Method object that is a legal combination of + * this method object and another one. + * + * This requires determining the exceptions declared by the + * combined method, which must be (only) all of the exceptions + * declared in both old Methods that may thrown in either of + * them. + */ + private Method mergeWith(Method other) { + if (!getName().equals(other.getName()) || + !getType().equals(other.getType())) + { + throw new Error("attempt to merge method \"" + + other.getNameAndDescriptor() + "\" with \"" + + getNameAndDescriptor()); + } + + Vector legalExceptions = new Vector(); + try { + collectCompatibleExceptions( + other.exceptions, exceptions, legalExceptions); + collectCompatibleExceptions( + exceptions, other.exceptions, legalExceptions); + } catch (ClassNotFound e) { + env.error(0, "class.not.found", e.name, + getClassDefinition().getName()); + return null; + } + + Method merged = (Method) clone(); + merged.exceptions = new ClassDeclaration[legalExceptions.size()]; + legalExceptions.copyInto(merged.exceptions); + + return merged; + } + + /** + * Add to the supplied list all exceptions in the "from" array + * that are subclasses of an exception in the "with" array. + */ + private void collectCompatibleExceptions(ClassDeclaration[] from, + ClassDeclaration[] with, + Vector list) + throws ClassNotFound + { + for (int i = 0; i < from.length; i++) { + ClassDefinition exceptionDef = from[i].getClassDefinition(env); + if (!list.contains(from[i])) { + for (int j = 0; j < with.length; j++) { + if (exceptionDef.subClassOf(env, with[j])) { + list.addElement(from[i]); + break; + } + } + } + } + } + + /** + * Compute the "method hash" of this remote method. The method + * hash is a long containing the first 64 bits of the SHA digest + * from the UTF encoded string of the method name and descriptor. + * + * REMIND: Should this method share implementation code with + * the outer class's computeInterfaceHash() method? + */ + private long computeMethodHash() { + long hash = 0; + ByteArrayOutputStream sink = new ByteArrayOutputStream(512); + try { + MessageDigest md = MessageDigest.getInstance("SHA"); + DataOutputStream out = new DataOutputStream( + new DigestOutputStream(sink, md)); + + String methodString = getNameAndDescriptor(); + /***** <DEBUG> */ + if (env.verbose()) { + System.out.println("[string used for method hash: \"" + + methodString + "\"]"); + } + /***** </DEBUG> */ + out.writeUTF(methodString); + + // use only the first 64 bits of the digest for the hash + out.flush(); + byte hashArray[] = md.digest(); + for (int i = 0; i < Math.min(8, hashArray.length); i++) { + hash += ((long) (hashArray[i] & 0xFF)) << (i * 8); + } + } catch (IOException e) { + throw new Error( + "unexpected exception computing intetrface hash: " + e); + } catch (NoSuchAlgorithmException e) { + throw new Error( + "unexpected exception computing intetrface hash: " + e); + } + + return hash; + } + } +} diff --git a/src/share/classes/sun/rmi/rmic/Util.java b/src/share/classes/sun/rmi/rmic/Util.java new file mode 100644 index 000000000..06a2ae343 --- /dev/null +++ b/src/share/classes/sun/rmi/rmic/Util.java @@ -0,0 +1,136 @@ +/* + * Copyright 1999-2007 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +/* + * Licensed Materials - Property of IBM + * RMI-IIOP v1.0 + * Copyright IBM Corp. 1998 1999 All Rights Reserved + * + */ + +package sun.rmi.rmic; + +import java.io.File; +import sun.tools.java.Identifier; + +/** + * Util provides static utility methods used by other rmic classes. + * + * WARNING: The contents of this source file are not part of any + * supported API. Code that depends on them does so at its own risk: + * they are subject to change or removal without notice. + * + * @author Bryan Atsatt + */ + +public class Util implements sun.rmi.rmic.Constants { + + /** + * Return the directory that should be used for output for a given + * class. + * @param theClass The fully qualified name of the class. + * @param rootDir The directory to use as the root of the + * package heirarchy. May be null, in which case the current + * working directory is used as the root. + */ + public static File getOutputDirectoryFor(Identifier theClass, + File rootDir, + BatchEnvironment env) { + + File outputDir = null; + String className = theClass.getFlatName().toString().replace('.', SIGC_INNERCLASS); + String qualifiedClassName = className; + String packagePath = null; + String packageName = theClass.getQualifier().toString(); + + if (packageName.length() > 0) { + qualifiedClassName = packageName + "." + className; + packagePath = packageName.replace('.', File.separatorChar); + } + + // Do we have a root directory? + + if (rootDir != null) { + + // Yes, do we have a package name? + + if (packagePath != null) { + + // Yes, so use it as the root. Open the directory... + + outputDir = new File(rootDir, packagePath); + + // Make sure the directory exists... + + ensureDirectory(outputDir,env); + + } else { + + // Default package, so use root as output dir... + + outputDir = rootDir; + } + } else { + + // No root directory. Get the current working directory... + + String workingDirPath = System.getProperty("user.dir"); + File workingDir = new File(workingDirPath); + + // Do we have a package name? + + if (packagePath == null) { + + // No, so use working directory... + + outputDir = workingDir; + + } else { + + // Yes, so use working directory as the root... + + outputDir = new File(workingDir, packagePath); + + // Make sure the directory exists... + + ensureDirectory(outputDir,env); + } + } + + // Finally, return the directory... + + return outputDir; + } + + private static void ensureDirectory (File dir, BatchEnvironment env) { + if (!dir.exists()) { + dir.mkdirs(); + if (!dir.exists()) { + env.error(0,"rmic.cannot.create.dir",dir.getAbsolutePath()); + throw new InternalError(); + } + } + } +} diff --git a/src/share/classes/sun/rmi/rmic/newrmic/BatchEnvironment.java b/src/share/classes/sun/rmi/rmic/newrmic/BatchEnvironment.java new file mode 100644 index 000000000..584e21ba0 --- /dev/null +++ b/src/share/classes/sun/rmi/rmic/newrmic/BatchEnvironment.java @@ -0,0 +1,145 @@ +/* + * Copyright 2003 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.rmic.newrmic; + +import com.sun.javadoc.ClassDoc; +import com.sun.javadoc.RootDoc; +import java.io.File; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static sun.rmi.rmic.newrmic.Constants.*; + +/** + * The environment for an rmic compilation batch. + * + * A BatchEnvironment contains a RootDoc, which is the entry point + * into the doclet environment for the associated rmic compilation + * batch. A BatchEnvironment collects the source files generated + * during the batch's execution, for eventual source code compilation + * and, possibly, deletion. Errors that occur during generation + * activity should be reported through the BatchEnvironment's "error" + * method. + * + * A protocol-specific generator class may require the use of a + * particular BatchEnvironment subclass for enhanced environment + * functionality. A BatchEnvironment subclass must declare a + * public constructor with one parameter of type RootDoc. + * + * WARNING: The contents of this source file are not part of any + * supported API. Code that depends on them does so at its own risk: + * they are subject to change or removal without notice. + * + * @author Peter Jones + **/ +public class BatchEnvironment { + + private final RootDoc rootDoc; + + /** cached ClassDoc for certain types used by rmic */ + private final ClassDoc docRemote; + private final ClassDoc docException; + private final ClassDoc docRemoteException; + private final ClassDoc docRuntimeException; + + private boolean verbose = false; + private final List<File> generatedFiles = new ArrayList<File>(); + + /** + * Creates a new BatchEnvironment with the specified RootDoc. + **/ + public BatchEnvironment(RootDoc rootDoc) { + this.rootDoc = rootDoc; + + /* + * Initialize cached ClassDoc for types used by rmic. Note + * that any of these could be null if the boot class path is + * incorrect, which could cause a NullPointerException later. + */ + docRemote = rootDoc().classNamed(REMOTE); + docException = rootDoc().classNamed(EXCEPTION); + docRemoteException = rootDoc().classNamed(REMOTE_EXCEPTION); + docRuntimeException = rootDoc().classNamed(RUNTIME_EXCEPTION); + } + + /** + * Returns the RootDoc for this environment. + **/ + public RootDoc rootDoc() { + return rootDoc; + } + + public ClassDoc docRemote() { return docRemote; } + public ClassDoc docException() { return docException; } + public ClassDoc docRemoteException() { return docRemoteException; } + public ClassDoc docRuntimeException() { return docRuntimeException; } + + /** + * Sets this environment's verbosity status. + **/ + public void setVerbose(boolean verbose) { + this.verbose = verbose; + } + + /** + * Returns this environment's verbosity status. + **/ + public boolean verbose() { + return verbose; + } + + /** + * Adds the specified file to the list of source files generated + * during this batch. + **/ + public void addGeneratedFile(File file) { + generatedFiles.add(file); + } + + /** + * Returns the list of files generated during this batch. + **/ + public List<File> generatedFiles() { + return Collections.unmodifiableList(generatedFiles); + } + + /** + * Outputs the specified (non-error) message. + **/ + public void output(String msg) { + rootDoc.printNotice(msg); + } + + /** + * Reports an error using the specified resource key and text + * formatting arguments. + **/ + public void error(String key, String... args) { + rootDoc.printError(Resources.getText(key, args)); + } +} diff --git a/src/share/classes/sun/rmi/rmic/newrmic/Constants.java b/src/share/classes/sun/rmi/rmic/newrmic/Constants.java new file mode 100644 index 000000000..30eb923fc --- /dev/null +++ b/src/share/classes/sun/rmi/rmic/newrmic/Constants.java @@ -0,0 +1,48 @@ +/* + * Copyright 2003 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.rmic.newrmic; + +/** + * Constants potentially useful to all rmic generators. + * + * WARNING: The contents of this source file are not part of any + * supported API. Code that depends on them does so at its own risk: + * they are subject to change or removal without notice. + * + * @author Peter Jones + **/ +public final class Constants { + + private Constants() { throw new AssertionError(); } + + /* + * fully-qualified names of types used by rmic + */ + public static final String REMOTE = "java.rmi.Remote"; + public static final String EXCEPTION = "java.lang.Exception"; + public static final String REMOTE_EXCEPTION = "java.rmi.RemoteException"; + public static final String RUNTIME_EXCEPTION = "java.lang.RuntimeException"; +} diff --git a/src/share/classes/sun/rmi/rmic/newrmic/Generator.java b/src/share/classes/sun/rmi/rmic/newrmic/Generator.java new file mode 100644 index 000000000..ee457597a --- /dev/null +++ b/src/share/classes/sun/rmi/rmic/newrmic/Generator.java @@ -0,0 +1,87 @@ +/* + * Copyright 2003 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.rmic.newrmic; + +import com.sun.javadoc.ClassDoc; +import java.io.File; +import java.util.Set; + +/** + * The interface to rmic back end implementations. Classes that + * implement this interface correspond to the various generation modes + * of rmic (JRMP, IIOP, IDL, etc.). + * + * A Generator instance corresponds to a particular rmic compilation + * batch, and its instance state represents the generator-specific + * command line options for that batch. Main will instantiate a + * generator class when the command line arguments indicate selection + * of the corresponding generation mode. Main will then invoke the + * "parseArgs" method to allow the generator to process any + * generator-specific command line options and set its instance state + * accordingly. + * + * WARNING: The contents of this source file are not part of any + * supported API. Code that depends on them does so at its own risk: + * they are subject to change or removal without notice. + * + * @author Peter Jones + **/ +public interface Generator { + + /** + * Processes the command line options specific to this generator. + * Processed options are set to null in the specified array. + * Returns true if successful or false if an error occurs. Errors + * are output to the specific Main instance. + **/ + public boolean parseArgs(String[] args, Main main); + + /** + * Returns the most specific environment class required by this + * generator. + **/ + public Class<? extends BatchEnvironment> envClass(); + + /** + * Returns the names of the classes that must be available through + * the doclet API in order for this generator to function. + **/ + public Set<String> bootstrapClassNames(); + + /** + * Generates the protocol-specific rmic output files for the + * specified remote class. This method is invoked once for each + * class or interface specified on the command line for the rmic + * compilation batch associated with this instance. + * + * Any generated source files (to be compiled with javac) are + * passed to the addGeneratedFile method of the specified + * BatchEnvironment. + **/ + public void generate(BatchEnvironment env, + ClassDoc inputClass, + File destDir); +} diff --git a/src/share/classes/sun/rmi/rmic/newrmic/IndentingWriter.java b/src/share/classes/sun/rmi/rmic/newrmic/IndentingWriter.java new file mode 100644 index 000000000..746551dd0 --- /dev/null +++ b/src/share/classes/sun/rmi/rmic/newrmic/IndentingWriter.java @@ -0,0 +1,291 @@ +/* + * Copyright 2003 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.rmic.newrmic; + +import java.io.Writer; +import java.io.BufferedWriter; +import java.io.IOException; + +/** + * A BufferedWriter that supports automatic indentation of lines of + * text written to the underlying Writer. + * + * Methods are provided for compact/convenient indenting in and out, + * writing text, and writing lines of text in various combinations. + * + * WARNING: The contents of this source file are not part of any + * supported API. Code that depends on them does so at its own risk: + * they are subject to change or removal without notice. + * + * @author Peter Jones + **/ +public class IndentingWriter extends BufferedWriter { + + /** number of spaces to change indent when indenting in or out */ + private final int indentStep; + + /** number of spaces to convert into tabs (use MAX_VALUE to disable) */ + private final int tabSize; + + /** true if the next character written is the first on a line */ + private boolean beginningOfLine = true; + + /** current number of spaces to prepend to lines */ + private int currentIndent = 0; + + /** + * Creates a new IndentingWriter that writes indented text to the + * given Writer. Use the default indent step of four spaces. + **/ + public IndentingWriter(Writer out) { + this(out, 4); + } + + /** + * Creates a new IndentingWriter that writes indented text to the + * given Writer and uses the supplied indent step. + **/ + public IndentingWriter(Writer out, int indentStep) { + this(out, indentStep, 8); + } + + /** + * Creates a new IndentingWriter that writes indented text to the + * given Writer and uses the supplied indent step and tab size. + **/ + public IndentingWriter(Writer out, int indentStep, int tabSize) { + super(out); + if (indentStep < 0) { + throw new IllegalArgumentException("negative indent step"); + } + if (tabSize < 0) { + throw new IllegalArgumentException("negative tab size"); + } + this.indentStep = indentStep; + this.tabSize = tabSize; + } + + /** + * Writes a single character. + **/ + public void write(int c) throws IOException { + checkWrite(); + super.write(c); + } + + /** + * Writes a portion of an array of characters. + **/ + public void write(char[] cbuf, int off, int len) throws IOException { + if (len > 0) { + checkWrite(); + } + super.write(cbuf, off, len); + } + + /** + * Writes a portion of a String. + **/ + public void write(String s, int off, int len) throws IOException { + if (len > 0) { + checkWrite(); + } + super.write(s, off, len); + } + + /** + * Writes a line separator. The next character written will be + * preceded by an indent. + **/ + public void newLine() throws IOException { + super.newLine(); + beginningOfLine = true; + } + + /** + * Checks if an indent needs to be written before writing the next + * character. + * + * The indent generation is optimized (and made consistent with + * certain coding conventions) by condensing groups of eight + * spaces into tab characters. + **/ + protected void checkWrite() throws IOException { + if (beginningOfLine) { + beginningOfLine = false; + int i = currentIndent; + while (i >= tabSize) { + super.write('\t'); + i -= tabSize; + } + while (i > 0) { + super.write(' '); + i--; + } + } + } + + /** + * Increases the current indent by the indent step. + **/ + protected void indentIn() { + currentIndent += indentStep; + } + + /** + * Decreases the current indent by the indent step. + **/ + protected void indentOut() { + currentIndent -= indentStep; + if (currentIndent < 0) + currentIndent = 0; + } + + /** + * Indents in. + **/ + public void pI() { + indentIn(); + } + + /** + * Indents out. + **/ + public void pO() { + indentOut(); + } + + /** + * Writes string. + **/ + public void p(String s) throws IOException { + write(s); + } + + /** + * Ends current line. + **/ + public void pln() throws IOException { + newLine(); + } + + /** + * Writes string; ends current line. + **/ + public void pln(String s) throws IOException { + p(s); + pln(); + } + + /** + * Writes string; ends current line; indents in. + **/ + public void plnI(String s) throws IOException { + p(s); + pln(); + pI(); + } + + /** + * Indents out; writes string. + **/ + public void pO(String s) throws IOException { + pO(); + p(s); + } + + /** + * Indents out; writes string; ends current line. + **/ + public void pOln(String s) throws IOException { + pO(s); + pln(); + } + + /** + * Indents out; writes string; ends current line; indents in. + * + * This method is useful for generating lines of code that both + * end and begin nested blocks, like "} else {". + **/ + public void pOlnI(String s) throws IOException { + pO(s); + pln(); + pI(); + } + + /** + * Writes object. + **/ + public void p(Object o) throws IOException { + write(o.toString()); + } + + /** + * Writes object; ends current line. + **/ + public void pln(Object o) throws IOException { + p(o.toString()); + pln(); + } + + /** + * Writes object; ends current line; indents in. + **/ + public void plnI(Object o) throws IOException { + p(o.toString()); + pln(); + pI(); + } + + /** + * Indents out; writes object. + **/ + public void pO(Object o) throws IOException { + pO(); + p(o.toString()); + } + + /** + * Indents out; writes object; ends current line. + **/ + public void pOln(Object o) throws IOException { + pO(o.toString()); + pln(); + } + + /** + * Indents out; writes object; ends current line; indents in. + * + * This method is useful for generating lines of code that both + * end and begin nested blocks, like "} else {". + **/ + public void pOlnI(Object o) throws IOException { + pO(o.toString()); + pln(); + pI(); + } +} diff --git a/src/share/classes/sun/rmi/rmic/newrmic/Main.java b/src/share/classes/sun/rmi/rmic/newrmic/Main.java new file mode 100644 index 000000000..e7dc7c899 --- /dev/null +++ b/src/share/classes/sun/rmi/rmic/newrmic/Main.java @@ -0,0 +1,689 @@ +/* + * Copyright 2003-2005 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.rmic.newrmic; + +import com.sun.javadoc.ClassDoc; +import com.sun.javadoc.RootDoc; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import sun.rmi.rmic.newrmic.jrmp.JrmpGenerator; +import sun.tools.util.CommandLine; + +/** + * The rmic front end. This class contains the "main" method for rmic + * command line invocation. + * + * A Main instance contains the stream to output error messages and + * other diagnostics to. + * + * An rmic compilation batch (for example, one rmic command line + * invocation) is executed by invoking the "compile" method of a Main + * instance. + * + * WARNING: The contents of this source file are not part of any + * supported API. Code that depends on them does so at its own risk: + * they are subject to change or removal without notice. + * + * NOTE: If and when there is a J2SE API for invoking SDK tools, this + * class should be updated to support that API. + * + * NOTE: This class is the front end for a "new" rmic implementation, + * which uses javadoc and the doclet API for reading class files and + * javac for compiling generated source files. This implementation is + * incomplete: it lacks any CORBA-based back end implementations, and + * thus the command line options "-idl", "-iiop", and their related + * options are not yet supported. The front end for the "old", + * oldjavac-based rmic implementation is sun.rmi.rmic.Main. + * + * @author Peter Jones + **/ +public class Main { + + /* + * Implementation note: + * + * In order to use the doclet API to read class files, much of + * this implementation of rmic executes as a doclet within an + * invocation of javadoc. This class is used as the doclet class + * for such javadoc invocations, via its static "start" and + * "optionLength" methods. There is one javadoc invocation per + * rmic compilation batch. + * + * The only guaranteed way to pass data to a doclet through a + * javadoc invocation is through doclet-specific options on the + * javadoc "command line". Rather than passing numerous pieces of + * individual data in string form as javadoc options, we use a + * single doclet-specific option ("-batchID") to pass a numeric + * identifier that uniquely identifies the rmic compilation batch + * that the javadoc invocation is for, and that identifier can + * then be used as a key in a global table to retrieve an object + * containing all of batch-specific data (rmic command line + * arguments, etc.). + */ + + /** guards "batchCount" */ + private static final Object batchCountLock = new Object(); + + /** number of batches run; used to generated batch IDs */ + private static long batchCount = 0; + + /** maps batch ID to batch data */ + private static final Map<Long,Batch> batchTable = + Collections.synchronizedMap(new HashMap<Long,Batch>()); + + /** stream to output error messages and other diagnostics to */ + private final PrintStream out; + + /** name of this program, to use in error messages */ + private final String program; + + /** + * Command line entry point. + **/ + public static void main(String[] args) { + Main rmic = new Main(System.err, "rmic"); + System.exit(rmic.compile(args) ? 0 : 1); + } + + /** + * Creates a Main instance that writes output to the specified + * stream. The specified program name is used in error messages. + **/ + public Main(OutputStream out, String program) { + this.out = out instanceof PrintStream ? + (PrintStream) out : new PrintStream(out); + this.program = program; + } + + /** + * Compiles a batch of input classes, as given by the specified + * command line arguments. Protocol-specific generators are + * determined by the choice options on the command line. Returns + * true if successful, or false if an error occurred. + * + * NOTE: This method is retained for transitional consistency with + * previous implementations. + **/ + public boolean compile(String[] args) { + long startTime = System.currentTimeMillis(); + + long batchID; + synchronized (batchCountLock) { + batchID = batchCount++; // assign batch ID + } + + // process command line + Batch batch = parseArgs(args); + if (batch == null) { + return false; // terminate if error occurred + } + + /* + * With the batch data retrievable in the global table, run + * javadoc to continue the rest of the batch's compliation as + * a doclet. + */ + boolean status; + try { + batchTable.put(batchID, batch); + status = invokeJavadoc(batch, batchID); + } finally { + batchTable.remove(batchID); + } + + if (batch.verbose) { + long deltaTime = System.currentTimeMillis() - startTime; + output(Resources.getText("rmic.done_in", + Long.toString(deltaTime))); + } + + return status; + } + + /** + * Prints the specified string to the output stream of this Main + * instance. + **/ + public void output(String msg) { + out.println(msg); + } + + /** + * Prints an error message to the output stream of this Main + * instance. The first argument is used as a key in rmic's + * resource bundle, and the rest of the arguments are used as + * arguments in the formatting of the resource string. + **/ + public void error(String msg, String... args) { + output(Resources.getText(msg, args)); + } + + /** + * Prints rmic's usage message to the output stream of this Main + * instance. + * + * This method is public so that it can be used by the "parseArgs" + * methods of Generator implementations. + **/ + public void usage() { + error("rmic.usage", program); + } + + /** + * Processes rmic command line arguments. Returns a Batch object + * representing the command line arguments if successful, or null + * if an error occurred. Processed elements of the args array are + * set to null. + **/ + private Batch parseArgs(String[] args) { + Batch batch = new Batch(); + + /* + * Pre-process command line for @file arguments. + */ + try { + args = CommandLine.parse(args); + } catch (FileNotFoundException e) { + error("rmic.cant.read", e.getMessage()); + return null; + } catch (IOException e) { + e.printStackTrace(out); + return null; + } + + for (int i = 0; i < args.length; i++) { + + if (args[i] == null) { + // already processed by a generator + continue; + + } else if (args[i].equals("-Xnew")) { + // we're already using the "new" implementation + args[i] = null; + + } else if (args[i].equals("-show")) { + // obselete: fail + error("rmic.option.unsupported", args[i]); + usage(); + return null; + + } else if (args[i].equals("-O")) { + // obselete: warn but tolerate + error("rmic.option.unsupported", args[i]); + args[i] = null; + + } else if (args[i].equals("-debug")) { + // obselete: warn but tolerate + error("rmic.option.unsupported", args[i]); + args[i] = null; + + } else if (args[i].equals("-depend")) { + // obselete: warn but tolerate + // REMIND: should this fail instead? + error("rmic.option.unsupported", args[i]); + args[i] = null; + + } else if (args[i].equals("-keep") || + args[i].equals("-keepgenerated")) + { + batch.keepGenerated = true; + args[i] = null; + + } else if (args[i].equals("-g")) { + batch.debug = true; + args[i] = null; + + } else if (args[i].equals("-nowarn")) { + batch.noWarn = true; + args[i] = null; + + } else if (args[i].equals("-nowrite")) { + batch.noWrite = true; + args[i] = null; + + } else if (args[i].equals("-verbose")) { + batch.verbose = true; + args[i] = null; + + } else if (args[i].equals("-Xnocompile")) { + batch.noCompile = true; + batch.keepGenerated = true; + args[i] = null; + + } else if (args[i].equals("-bootclasspath")) { + if ((i + 1) >= args.length) { + error("rmic.option.requires.argument", args[i]); + usage(); + return null; + } + if (batch.bootClassPath != null) { + error("rmic.option.already.seen", args[i]); + usage(); + return null; + } + args[i] = null; + batch.bootClassPath = args[++i]; + assert batch.bootClassPath != null; + args[i] = null; + + } else if (args[i].equals("-extdirs")) { + if ((i + 1) >= args.length) { + error("rmic.option.requires.argument", args[i]); + usage(); + return null; + } + if (batch.extDirs != null) { + error("rmic.option.already.seen", args[i]); + usage(); + return null; + } + args[i] = null; + batch.extDirs = args[++i]; + assert batch.extDirs != null; + args[i] = null; + + } else if (args[i].equals("-classpath")) { + if ((i + 1) >= args.length) { + error("rmic.option.requires.argument", args[i]); + usage(); + return null; + } + if (batch.classPath != null) { + error("rmic.option.already.seen", args[i]); + usage(); + return null; + } + args[i] = null; + batch.classPath = args[++i]; + assert batch.classPath != null; + args[i] = null; + + } else if (args[i].equals("-d")) { + if ((i + 1) >= args.length) { + error("rmic.option.requires.argument", args[i]); + usage(); + return null; + } + if (batch.destDir != null) { + error("rmic.option.already.seen", args[i]); + usage(); + return null; + } + args[i] = null; + batch.destDir = new File(args[++i]); + assert batch.destDir != null; + args[i] = null; + if (!batch.destDir.exists()) { + error("rmic.no.such.directory", batch.destDir.getPath()); + usage(); + return null; + } + + } else if (args[i].equals("-v1.1") || + args[i].equals("-vcompat") || + args[i].equals("-v1.2")) + { + Generator gen = new JrmpGenerator(); + batch.generators.add(gen); + // JrmpGenerator only requires base BatchEnvironment class + if (!gen.parseArgs(args, this)) { + return null; + } + + } else if (args[i].equalsIgnoreCase("-iiop")) { + error("rmic.option.unimplemented", args[i]); + return null; + + // Generator gen = new IiopGenerator(); + // batch.generators.add(gen); + // if (!batch.envClass.isAssignableFrom(gen.envClass())) { + // error("rmic.cannot.use.both", + // batch.envClass.getName(), gen.envClass().getName()); + // return null; + // } + // batch.envClass = gen.envClass(); + // if (!gen.parseArgs(args, this)) { + // return null; + // } + + } else if (args[i].equalsIgnoreCase("-idl")) { + error("rmic.option.unimplemented", args[i]); + return null; + + // see implementation sketch above + + } else if (args[i].equalsIgnoreCase("-xprint")) { + error("rmic.option.unimplemented", args[i]); + return null; + + // see implementation sketch above + } + } + + /* + * At this point, all that remains non-null in the args + * array are input class names or illegal options. + */ + for (int i = 0; i < args.length; i++) { + if (args[i] != null) { + if (args[i].startsWith("-")) { + error("rmic.no.such.option", args[i]); + usage(); + return null; + } else { + batch.classes.add(args[i]); + } + } + } + if (batch.classes.isEmpty()) { + usage(); + return null; + } + + /* + * If options did not specify at least one protocol-specific + * generator, then JRMP is the default. + */ + if (batch.generators.isEmpty()) { + batch.generators.add(new JrmpGenerator()); + } + return batch; + } + + /** + * Doclet class entry point. + **/ + public static boolean start(RootDoc rootDoc) { + + /* + * Find batch ID among javadoc options, and retrieve + * corresponding batch data from global table. + */ + long batchID = -1; + for (String[] option : rootDoc.options()) { + if (option[0].equals("-batchID")) { + try { + batchID = Long.parseLong(option[1]); + } catch (NumberFormatException e) { + throw new AssertionError(e); + } + } + } + Batch batch = batchTable.get(batchID); + assert batch != null; + + /* + * Construct batch environment using class agreed upon by + * generator implementations. + */ + BatchEnvironment env; + try { + Constructor<? extends BatchEnvironment> cons = + batch.envClass.getConstructor(new Class[] { RootDoc.class }); + env = cons.newInstance(rootDoc); + } catch (NoSuchMethodException e) { + throw new AssertionError(e); + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } catch (InstantiationException e) { + throw new AssertionError(e); + } catch (InvocationTargetException e) { + throw new AssertionError(e); + } + + env.setVerbose(batch.verbose); + + /* + * Determine the destination directory (the top of the package + * hierarchy) for the output of this batch; if no destination + * directory was specified on the command line, then the + * default is the current working directory. + */ + File destDir = batch.destDir; + if (destDir == null) { + destDir = new File(System.getProperty("user.dir")); + } + + /* + * Run each input class through each generator. + */ + for (String inputClassName : batch.classes) { + ClassDoc inputClass = rootDoc.classNamed(inputClassName); + try { + for (Generator gen : batch.generators) { + gen.generate(env, inputClass, destDir); + } + } catch (NullPointerException e) { + /* + * We assume that this means that some class that was + * needed (perhaps even a bootstrap class) was not + * found, and that javadoc has already reported this + * as an error. There is nothing for us to do here + * but try to continue with the next input class. + * + * REMIND: More explicit error checking throughout + * would be preferable, however. + */ + } + } + + /* + * Compile any generated source files, if configured to do so. + */ + boolean status = true; + List<File> generatedFiles = env.generatedFiles(); + if (!batch.noCompile && !batch.noWrite && !generatedFiles.isEmpty()) { + status = batch.enclosingMain().invokeJavac(batch, generatedFiles); + } + + /* + * Delete any generated source files, if configured to do so. + */ + if (!batch.keepGenerated) { + for (File file : generatedFiles) { + file.delete(); + } + } + + return status; + } + + /** + * Doclet class method that indicates that this doclet class + * recognizes (only) the "-batchID" option on the javadoc command + * line, and that the "-batchID" option comprises two arguments on + * the javadoc command line. + **/ + public static int optionLength(String option) { + if (option.equals("-batchID")) { + return 2; + } else { + return 0; + } + } + + /** + * Runs the javadoc tool to invoke this class as a doclet, passing + * command line options derived from the specified batch data and + * indicating the specified batch ID. + * + * NOTE: This method currently uses a J2SE-internal API to run + * javadoc. If and when there is a J2SE API for invoking SDK + * tools, this method should be updated to use that API instead. + **/ + private boolean invokeJavadoc(Batch batch, long batchID) { + List<String> javadocArgs = new ArrayList<String>(); + + // include all types, regardless of language-level access + javadocArgs.add("-private"); + + // inputs are class names, not source files + javadocArgs.add("-Xclasses"); + + // reproduce relevant options from rmic invocation + if (batch.verbose) { + javadocArgs.add("-verbose"); + } + if (batch.bootClassPath != null) { + javadocArgs.add("-bootclasspath"); + javadocArgs.add(batch.bootClassPath); + } + if (batch.extDirs != null) { + javadocArgs.add("-extdirs"); + javadocArgs.add(batch.extDirs); + } + if (batch.classPath != null) { + javadocArgs.add("-classpath"); + javadocArgs.add(batch.classPath); + } + + // specify batch ID + javadocArgs.add("-batchID"); + javadocArgs.add(Long.toString(batchID)); + + /* + * Run javadoc on union of rmic input classes and all + * generators' bootstrap classes, so that they will all be + * available to the doclet code. + */ + Set<String> classNames = new HashSet<String>(); + for (Generator gen : batch.generators) { + classNames.addAll(gen.bootstrapClassNames()); + } + classNames.addAll(batch.classes); + for (String s : classNames) { + javadocArgs.add(s); + } + + // run javadoc with our program name and output stream + int status = com.sun.tools.javadoc.Main.execute( + program, + new PrintWriter(out, true), + new PrintWriter(out, true), + new PrintWriter(out, true), + this.getClass().getName(), // doclet class is this class + javadocArgs.toArray(new String[javadocArgs.size()])); + return status == 0; + } + + /** + * Runs the javac tool to compile the specified source files, + * passing command line options derived from the specified batch + * data. + * + * NOTE: This method currently uses a J2SE-internal API to run + * javac. If and when there is a J2SE API for invoking SDK tools, + * this method should be updated to use that API instead. + **/ + private boolean invokeJavac(Batch batch, List<File> files) { + List<String> javacArgs = new ArrayList<String>(); + + // rmic never wants to display javac warnings + javacArgs.add("-nowarn"); + + // reproduce relevant options from rmic invocation + if (batch.debug) { + javacArgs.add("-g"); + } + if (batch.verbose) { + javacArgs.add("-verbose"); + } + if (batch.bootClassPath != null) { + javacArgs.add("-bootclasspath"); + javacArgs.add(batch.bootClassPath); + } + if (batch.extDirs != null) { + javacArgs.add("-extdirs"); + javacArgs.add(batch.extDirs); + } + if (batch.classPath != null) { + javacArgs.add("-classpath"); + javacArgs.add(batch.classPath); + } + + /* + * For now, rmic still always produces class files that have a + * class file format version compatible with JDK 1.1. + */ + javacArgs.add("-source"); + javacArgs.add("1.3"); + javacArgs.add("-target"); + javacArgs.add("1.1"); + + // add source files to compile + for (File file : files) { + javacArgs.add(file.getPath()); + } + + // run javac with our output stream + int status = com.sun.tools.javac.Main.compile( + javacArgs.toArray(new String[javacArgs.size()]), + new PrintWriter(out, true)); + return status == 0; + } + + /** + * The data for an rmic compliation batch: the processed command + * line arguments. + **/ + private class Batch { + boolean keepGenerated = false; // -keep or -keepgenerated + boolean debug = false; // -g + boolean noWarn = false; // -nowarn + boolean noWrite = false; // -nowrite + boolean verbose = false; // -verbose + boolean noCompile = false; // -Xnocompile + String bootClassPath = null; // -bootclasspath + String extDirs = null; // -extdirs + String classPath = null; // -classpath + File destDir = null; // -d + List<Generator> generators = new ArrayList<Generator>(); + Class<? extends BatchEnvironment> envClass = BatchEnvironment.class; + List<String> classes = new ArrayList<String>(); + + Batch() { } + + /** + * Returns the Main instance for this batch. + **/ + Main enclosingMain() { + return Main.this; + } + } +} diff --git a/src/share/classes/sun/rmi/rmic/newrmic/Resources.java b/src/share/classes/sun/rmi/rmic/newrmic/Resources.java new file mode 100644 index 000000000..fa245589c --- /dev/null +++ b/src/share/classes/sun/rmi/rmic/newrmic/Resources.java @@ -0,0 +1,95 @@ +/* + * Copyright 2003 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.rmic.newrmic; + +import java.text.MessageFormat; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +/** + * Provides resource support for rmic. + * + * WARNING: The contents of this source file are not part of any + * supported API. Code that depends on them does so at its own risk: + * they are subject to change or removal without notice. + * + * @author Peter Jones + **/ +public final class Resources { + + private static ResourceBundle resources = null; + private static ResourceBundle resourcesExt = null; + static { + try { + resources = + ResourceBundle.getBundle("sun.rmi.rmic.resources.rmic"); + } catch (MissingResourceException e) { + // gracefully handle this later + } + try { + resourcesExt = + ResourceBundle.getBundle("sun.rmi.rmic.resources.rmicext"); + } catch (MissingResourceException e) { + // OK if this isn't found + } + } + + private Resources() { throw new AssertionError(); } + + /** + * Returns the text of the rmic resource for the specified key + * formatted with the specified arguments. + **/ + public static String getText(String key, String... args) { + String format = getString(key); + if (format == null) { + format = "missing resource key: key = \"" + key + "\", " + + "arguments = \"{0}\", \"{1}\", \"{2}\""; + } + return MessageFormat.format(format, args); + } + + /** + * Returns the rmic resource string for the specified key. + **/ + private static String getString(String key) { + if (resourcesExt != null) { + try { + return resourcesExt.getString(key); + } catch (MissingResourceException e) { + } + } + if (resources != null) { + try { + return resources.getString(key); + } catch (MissingResourceException e) { + return null; + } + } + return "missing resource bundle: key = \"" + key + "\", " + + "arguments = \"{0}\", \"{1}\", \"{2}\""; + } +} diff --git a/src/share/classes/sun/rmi/rmic/newrmic/jrmp/Constants.java b/src/share/classes/sun/rmi/rmic/newrmic/jrmp/Constants.java new file mode 100644 index 000000000..1275a5e2f --- /dev/null +++ b/src/share/classes/sun/rmi/rmic/newrmic/jrmp/Constants.java @@ -0,0 +1,70 @@ +/* + * Copyright 2003 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.rmic.newrmic.jrmp; + +/** + * Constants specific to the JRMP rmic generator. + * + * WARNING: The contents of this source file are not part of any + * supported API. Code that depends on them does so at its own risk: + * they are subject to change or removal without notice. + * + * @author Peter Jones + **/ +final class Constants { + + private Constants() { throw new AssertionError(); } + + /* + * fully-qualified names of types used by rmic + */ + static final String REMOTE_OBJECT = "java.rmi.server.RemoteObject"; + static final String REMOTE_STUB = "java.rmi.server.RemoteStub"; + static final String REMOTE_REF = "java.rmi.server.RemoteRef"; + static final String OPERATION = "java.rmi.server.Operation"; + static final String SKELETON = "java.rmi.server.Skeleton"; + static final String SKELETON_MISMATCH_EXCEPTION = + "java.rmi.server.SkeletonMismatchException"; + static final String REMOTE_CALL = "java.rmi.server.RemoteCall"; + static final String MARSHAL_EXCEPTION = "java.rmi.MarshalException"; + static final String UNMARSHAL_EXCEPTION = "java.rmi.UnmarshalException"; + static final String UNEXPECTED_EXCEPTION = "java.rmi.UnexpectedException"; + + /* + * stub protocol versions + */ + enum StubVersion { V1_1, VCOMPAT, V1_2 }; + + /* + * serialVersionUID for all stubs that can use 1.2 protocol + */ + static final long STUB_SERIAL_VERSION_UID = 2; + + /* + * version number used to seed interface hash computation + */ + static final int INTERFACE_HASH_STUB_VERSION = 1; +} diff --git a/src/share/classes/sun/rmi/rmic/newrmic/jrmp/JrmpGenerator.java b/src/share/classes/sun/rmi/rmic/newrmic/jrmp/JrmpGenerator.java new file mode 100644 index 000000000..49e83a4bd --- /dev/null +++ b/src/share/classes/sun/rmi/rmic/newrmic/jrmp/JrmpGenerator.java @@ -0,0 +1,226 @@ +/* + * Copyright 2003-2005 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.rmic.newrmic.jrmp; + +import com.sun.javadoc.ClassDoc; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import sun.rmi.rmic.newrmic.BatchEnvironment; +import sun.rmi.rmic.newrmic.Generator; +import sun.rmi.rmic.newrmic.IndentingWriter; +import sun.rmi.rmic.newrmic.Main; +import sun.rmi.rmic.newrmic.Resources; + +import static sun.rmi.rmic.newrmic.jrmp.Constants.*; + +/** + * JRMP rmic back end; generates source code for JRMP stub and + * skeleton classes. + * + * WARNING: The contents of this source file are not part of any + * supported API. Code that depends on them does so at its own risk: + * they are subject to change or removal without notice. + * + * @author Peter Jones + **/ +public class JrmpGenerator implements Generator { + + private static final Map<String,StubVersion> versionOptions = + new HashMap<String,StubVersion>(); + static { + versionOptions.put("-v1.1", StubVersion.V1_1); + versionOptions.put("-vcompat", StubVersion.VCOMPAT); + versionOptions.put("-v1.2", StubVersion.V1_2); + } + + private static final Set<String> bootstrapClassNames = + new HashSet<String>(); + static { + bootstrapClassNames.add("java.lang.Exception"); + bootstrapClassNames.add("java.rmi.Remote"); + bootstrapClassNames.add("java.rmi.RemoteException"); + bootstrapClassNames.add("java.lang.RuntimeException"); + }; + + /** version of the JRMP stub protocol to generate code for */ + private StubVersion version = StubVersion.V1_2; // default is -v1.2 + + /** + * Creates a new JrmpGenerator. + **/ + public JrmpGenerator() { } + + /** + * The JRMP generator recognizes command line options for + * selecting the JRMP stub protocol version to generate classes + * for. Only one such option is allowed. + **/ + public boolean parseArgs(String[] args, Main main) { + String explicitVersion = null; + for (int i = 0; i < args.length; i++) { + String arg = args[i]; + if (versionOptions.containsKey(arg)) { + if (explicitVersion != null && !explicitVersion.equals(arg)) { + main.error("rmic.cannot.use.both", explicitVersion, arg); + return false; + } + explicitVersion = arg; + version = versionOptions.get(arg); + args[i] = null; + } + } + return true; + } + + /** + * The JRMP generator does not require an environment class more + * specific than BatchEnvironment. + **/ + public Class<? extends BatchEnvironment> envClass() { + return BatchEnvironment.class; + } + + public Set<String> bootstrapClassNames() { + return Collections.unmodifiableSet(bootstrapClassNames); + } + + /** + * Generates the source file(s) for the JRMP stub class and + * (optionally) skeleton class for the specified remote + * implementation class. + **/ + public void generate(BatchEnvironment env, + ClassDoc inputClass, + File destDir) + { + RemoteClass remoteClass = RemoteClass.forClass(env, inputClass); + if (remoteClass == null) { + return; // an error must have occurred + } + + StubSkeletonWriter writer = + new StubSkeletonWriter(env, remoteClass, version); + + File stubFile = sourceFileForClass(writer.stubClassName(), destDir); + try { + IndentingWriter out = new IndentingWriter( + new OutputStreamWriter(new FileOutputStream(stubFile))); + writer.writeStub(out); + out.close(); + if (env.verbose()) { + env.output(Resources.getText("rmic.wrote", + stubFile.getPath())); + } + env.addGeneratedFile(stubFile); + } catch (IOException e) { + env.error("rmic.cant.write", stubFile.toString()); + return; + } + + File skeletonFile = + sourceFileForClass(writer.skeletonClassName(), destDir); + if (version == StubVersion.V1_1 || + version == StubVersion.VCOMPAT) + { + try { + IndentingWriter out = new IndentingWriter( + new OutputStreamWriter( + new FileOutputStream(skeletonFile))); + writer.writeSkeleton(out); + out.close(); + if (env.verbose()) { + env.output(Resources.getText("rmic.wrote", + skeletonFile.getPath())); + } + env.addGeneratedFile(skeletonFile); + } catch (IOException e) { + env.error("rmic.cant.write", skeletonFile.toString()); + return; + } + } else { + /* + * If skeleton files are not being generated for this run, + * delete old skeleton source or class files for this + * remote implementation class that were (presumably) left + * over from previous runs, to avoid user confusion from + * extraneous or inconsistent generated files. + */ + File skeletonClassFile = + classFileForClass(writer.skeletonClassName(), destDir); + + skeletonFile.delete(); // ignore failures (no big deal) + skeletonClassFile.delete(); + } + } + + + /** + * Returns the File object to be used as the source file for a + * class with the specified binary name, with the specified + * destination directory as the top of the package hierarchy. + **/ + private File sourceFileForClass(String binaryName, File destDir) { + return fileForClass(binaryName, destDir, ".java"); + } + + /** + * Returns the File object to be used as the class file for a + * class with the specified binary name, with the supplied + * destination directory as the top of the package hierarchy. + **/ + private File classFileForClass(String binaryName, File destDir) { + return fileForClass(binaryName, destDir, ".class"); + } + + private File fileForClass(String binaryName, File destDir, String ext) { + int i = binaryName.lastIndexOf('.'); + String classFileName = binaryName.substring(i + 1) + ext; + if (i != -1) { + String packageName = binaryName.substring(0, i); + String packagePath = packageName.replace('.', File.separatorChar); + File packageDir = new File(destDir, packagePath); + /* + * Make sure that the directory for this package exists. + * We assume that the caller has verified that the top- + * level destination directory exists, so we need not + * worry about creating it unintentionally. + */ + if (!packageDir.exists()) { + packageDir.mkdirs(); + } + return new File(packageDir, classFileName); + } else { + return new File(destDir, classFileName); + } + } +} diff --git a/src/share/classes/sun/rmi/rmic/newrmic/jrmp/RemoteClass.java b/src/share/classes/sun/rmi/rmic/newrmic/jrmp/RemoteClass.java new file mode 100644 index 000000000..f22a652df --- /dev/null +++ b/src/share/classes/sun/rmi/rmic/newrmic/jrmp/RemoteClass.java @@ -0,0 +1,710 @@ +/* + * Copyright 2003 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.rmic.newrmic.jrmp; + +import com.sun.javadoc.ClassDoc; +import com.sun.javadoc.MethodDoc; +import com.sun.javadoc.Parameter; +import com.sun.javadoc.Type; +import java.io.IOException; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.security.MessageDigest; +import java.security.DigestOutputStream; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.HashMap; +import java.util.Map; +import sun.rmi.rmic.newrmic.BatchEnvironment; + +import static sun.rmi.rmic.newrmic.Constants.*; +import static sun.rmi.rmic.newrmic.jrmp.Constants.*; + +/** + * Encapsulates RMI-specific information about a remote implementation + * class (a class that implements one or more remote interfaces). + * + * WARNING: The contents of this source file are not part of any + * supported API. Code that depends on them does so at its own risk: + * they are subject to change or removal without notice. + * + * @author Peter Jones + **/ +final class RemoteClass { + + /** rmic environment for this object */ + private final BatchEnvironment env; + + /** the remote implementation class this object represents */ + private final ClassDoc implClass; + + /** remote interfaces implemented by this class */ + private ClassDoc[] remoteInterfaces; + + /** the remote methods of this class */ + private Method[] remoteMethods; + + /** stub/skeleton "interface hash" for this class */ + private long interfaceHash; + + /** + * Creates a RemoteClass instance that represents the RMI-specific + * information about the specified remote implementation class. + * + * If the class is not a valid remote implementation class or if + * some other error occurs, the return value will be null, and + * errors will have been reported to the supplied + * BatchEnvironment. + **/ + static RemoteClass forClass(BatchEnvironment env, ClassDoc implClass) { + RemoteClass remoteClass = new RemoteClass(env, implClass); + if (remoteClass.init()) { + return remoteClass; + } else { + return null; + } + } + + /** + * Creates a RemoteClass instance for the specified class. The + * resulting object is not yet initialized. + **/ + private RemoteClass(BatchEnvironment env, ClassDoc implClass) { + this.env = env; + this.implClass = implClass; + } + + /** + * Returns the ClassDoc for this remote implementation class. + **/ + ClassDoc classDoc() { + return implClass; + } + + /** + * Returns the remote interfaces implemented by this remote + * implementation class. + * + * A remote interface is an interface that is a subinterface of + * java.rmi.Remote. The remote interfaces of a class are the + * direct superinterfaces of the class and all of its superclasses + * that are remote interfaces. + * + * The order of the array returned is arbitrary, and some elements + * may be superfluous (i.e., superinterfaces of other interfaces + * in the array). + **/ + ClassDoc[] remoteInterfaces() { + return (ClassDoc[]) remoteInterfaces.clone(); + } + + /** + * Returns an array of RemoteClass.Method objects representing all + * of the remote methods of this remote implementation class (all + * of the member methods of the class's remote interfaces). + * + * The methods in the array are ordered according to a comparison + * of strings consisting of their name followed by their + * descriptor, so each method's index in the array corresponds to + * its "operation number" in the JDK 1.1 version of the JRMP + * stub/skeleton protocol. + **/ + Method[] remoteMethods() { + return (Method[]) remoteMethods.clone(); + } + + /** + * Returns the "interface hash" used to match a stub/skeleton pair + * for this remote implementation class in the JDK 1.1 version of + * the JRMP stub/skeleton protocol. + **/ + long interfaceHash() { + return interfaceHash; + } + + /** + * Validates this remote implementation class and computes the + * RMI-specific information. Returns true if successful, or false + * if an error occurred. + **/ + private boolean init() { + /* + * Verify that it is really a class, not an interface. + */ + if (implClass.isInterface()) { + env.error("rmic.cant.make.stubs.for.interface", + implClass.qualifiedName()); + return false; + } + + /* + * Find all of the remote interfaces of our remote + * implementation class-- for each class up the superclass + * chain, add each directly-implemented interface that somehow + * extends Remote to a list. + */ + List<ClassDoc> remotesImplemented = new ArrayList<ClassDoc>(); + for (ClassDoc cl = implClass; cl != null; cl = cl.superclass()) { + for (ClassDoc intf : cl.interfaces()) { + /* + * Add interface to the list if it extends Remote and + * it is not already there. + */ + if (!remotesImplemented.contains(intf) && + intf.subclassOf(env.docRemote())) + { + remotesImplemented.add(intf); + if (env.verbose()) { + env.output("[found remote interface: " + + intf.qualifiedName() + "]"); + } + } + } + + /* + * Verify that the candidate remote implementation class + * implements at least one remote interface directly. + */ + if (cl == implClass && remotesImplemented.isEmpty()) { + if (implClass.subclassOf(env.docRemote())) { + /* + * This error message is used if the class does + * implement a remote interface through one of its + * superclasses, but not directly. + */ + env.error("rmic.must.implement.remote.directly", + implClass.qualifiedName()); + } else { + /* + * This error message is used if the class does + * not implement a remote interface at all. + */ + env.error("rmic.must.implement.remote", + implClass.qualifiedName()); + } + return false; + } + } + + /* + * Convert list of remote interfaces to an array + * (order is not important for this array). + */ + remoteInterfaces = + remotesImplemented.toArray( + new ClassDoc[remotesImplemented.size()]); + + /* + * Collect the methods from all of the remote interfaces into + * a table, which maps from method name-and-descriptor string + * to Method object. + */ + Map<String,Method> methods = new HashMap<String,Method>(); + boolean errors = false; + for (ClassDoc intf : remotesImplemented) { + if (!collectRemoteMethods(intf, methods)) { + /* + * Continue iterating despite errors in order to + * generate more complete error report. + */ + errors = true; + } + } + if (errors) { + return false; + } + + /* + * Sort table of remote methods into an array. The elements + * are sorted in ascending order of the string of the method's + * name and descriptor, so that each elements index is equal + * to its operation number in the JDK 1.1 version of the JRMP + * stub/skeleton protocol. + */ + String[] orderedKeys = + methods.keySet().toArray(new String[methods.size()]); + Arrays.sort(orderedKeys); + remoteMethods = new Method[methods.size()]; + for (int i = 0; i < remoteMethods.length; i++) { + remoteMethods[i] = methods.get(orderedKeys[i]); + if (env.verbose()) { + String msg = "[found remote method <" + i + ">: " + + remoteMethods[i].operationString(); + ClassDoc[] exceptions = remoteMethods[i].exceptionTypes(); + if (exceptions.length > 0) { + msg += " throws "; + for (int j = 0; j < exceptions.length; j++) { + if (j > 0) { + msg += ", "; + } + msg += exceptions[j].qualifiedName(); + } + } + msg += "\n\tname and descriptor = \"" + + remoteMethods[i].nameAndDescriptor(); + msg += "\n\tmethod hash = " + + remoteMethods[i].methodHash() + "]"; + env.output(msg); + } + } + + /* + * Finally, pre-compute the interface hash to be used by + * stubs/skeletons for this remote class in the JDK 1.1 + * version of the JRMP stub/skeleton protocol. + */ + interfaceHash = computeInterfaceHash(); + + return true; + } + + /** + * Collects and validates all methods from the specified interface + * and all of its superinterfaces as remote methods. Remote + * methods are added to the supplied table. Returns true if + * successful, or false if an error occurred. + **/ + private boolean collectRemoteMethods(ClassDoc intf, + Map<String,Method> table) + { + if (!intf.isInterface()) { + throw new AssertionError( + intf.qualifiedName() + " not an interface"); + } + + boolean errors = false; + + /* + * Search interface's declared methods. + */ + nextMethod: + for (MethodDoc method : intf.methods()) { + + /* + * Verify that each method throws RemoteException (or a + * superclass of RemoteException). + */ + boolean hasRemoteException = false; + for (ClassDoc ex : method.thrownExceptions()) { + if (env.docRemoteException().subclassOf(ex)) { + hasRemoteException = true; + break; + } + } + + /* + * If this method did not throw RemoteException as required, + * generate the error but continue, so that multiple such + * errors can be reported. + */ + if (!hasRemoteException) { + env.error("rmic.must.throw.remoteexception", + intf.qualifiedName(), + method.name() + method.signature()); + errors = true; + continue nextMethod; + } + + /* + * Verify that the implementation of this method throws only + * java.lang.Exception or its subclasses (fix bugid 4092486). + * JRMP does not support remote methods throwing + * java.lang.Throwable or other subclasses. + */ + MethodDoc implMethod = findImplMethod(method); + if (implMethod != null) { // should not be null + for (ClassDoc ex : implMethod.thrownExceptions()) { + if (!ex.subclassOf(env.docException())) { + env.error("rmic.must.only.throw.exception", + implMethod.name() + implMethod.signature(), + ex.qualifiedName()); + errors = true; + continue nextMethod; + } + } + } + + /* + * Create RemoteClass.Method object to represent this method + * found in a remote interface. + */ + Method newMethod = new Method(method); + + /* + * Store remote method's representation in the table of + * remote methods found, keyed by its name and descriptor. + * + * If the table already contains an entry with the same + * method name and descriptor, then we must replace the + * old entry with a Method object that represents a legal + * combination of the old and the new methods; + * specifically, the combined method must have a throws + * clause that contains (only) all of the checked + * exceptions that can be thrown by both the old and the + * new method (see bugid 4070653). + */ + String key = newMethod.nameAndDescriptor(); + Method oldMethod = table.get(key); + if (oldMethod != null) { + newMethod = newMethod.mergeWith(oldMethod); + } + table.put(key, newMethod); + } + + /* + * Recursively collect methods for all superinterfaces. + */ + for (ClassDoc superintf : intf.interfaces()) { + if (!collectRemoteMethods(superintf, table)) { + errors = true; + } + } + + return !errors; + } + + /** + * Returns the MethodDoc for the method of this remote + * implementation class that implements the specified remote + * method of a remote interface. Returns null if no matching + * method was found in this remote implementation class. + **/ + private MethodDoc findImplMethod(MethodDoc interfaceMethod) { + String name = interfaceMethod.name(); + String desc = Util.methodDescriptorOf(interfaceMethod); + for (MethodDoc implMethod : implClass.methods()) { + if (name.equals(implMethod.name()) && + desc.equals(Util.methodDescriptorOf(implMethod))) + { + return implMethod; + } + } + return null; + } + + /** + * Computes the "interface hash" of the stub/skeleton pair for + * this remote implementation class. This is the 64-bit value + * used to enforce compatibility between a stub class and a + * skeleton class in the JDK 1.1 version of the JRMP stub/skeleton + * protocol. + * + * It is calculated using the first 64 bits of an SHA digest. The + * digest is of a stream consisting of the following data: + * (int) stub version number, always 1 + * for each remote method, in order of operation number: + * (UTF-8) method name + * (UTF-8) method descriptor + * for each declared exception, in alphabetical name order: + * (UTF-8) name of exception class + * (where "UTF-8" includes a 16-bit length prefix as written by + * java.io.DataOutput.writeUTF). + **/ + private long computeInterfaceHash() { + long hash = 0; + ByteArrayOutputStream sink = new ByteArrayOutputStream(512); + try { + MessageDigest md = MessageDigest.getInstance("SHA"); + DataOutputStream out = new DataOutputStream( + new DigestOutputStream(sink, md)); + + out.writeInt(INTERFACE_HASH_STUB_VERSION); + + for (Method method : remoteMethods) { + MethodDoc methodDoc = method.methodDoc(); + + out.writeUTF(methodDoc.name()); + out.writeUTF(Util.methodDescriptorOf(methodDoc)); + // descriptors already use binary names + + ClassDoc exceptions[] = methodDoc.thrownExceptions(); + Arrays.sort(exceptions, new ClassDocComparator()); + for (ClassDoc ex : exceptions) { + out.writeUTF(Util.binaryNameOf(ex)); + } + } + out.flush(); + + // use only the first 64 bits of the digest for the hash + byte hashArray[] = md.digest(); + for (int i = 0; i < Math.min(8, hashArray.length); i++) { + hash += ((long) (hashArray[i] & 0xFF)) << (i * 8); + } + } catch (IOException e) { + throw new AssertionError(e); + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(e); + } + + return hash; + } + + /** + * Compares ClassDoc instances according to the lexicographic + * order of their binary names. + **/ + private static class ClassDocComparator implements Comparator<ClassDoc> { + public int compare(ClassDoc o1, ClassDoc o2) { + return Util.binaryNameOf(o1).compareTo(Util.binaryNameOf(o2)); + } + } + + /** + * Encapsulates RMI-specific information about a particular remote + * method in the remote implementation class represented by the + * enclosing RemoteClass. + **/ + final class Method implements Cloneable { + + /** + * MethodDoc for this remove method, from one of the remote + * interfaces that this method was found in. + * + * Note that this MethodDoc may be only one of multiple that + * correspond to this remote method object, if multiple of + * this class's remote interfaces contain methods with the + * same name and descriptor. Therefore, this MethodDoc may + * declare more exceptions thrown that this remote method + * does. + **/ + private final MethodDoc methodDoc; + + /** java.rmi.server.Operation string for this remote method */ + private final String operationString; + + /** name and descriptor of this remote method */ + private final String nameAndDescriptor; + + /** JRMP "method hash" for this remote method */ + private final long methodHash; + + /** + * Exceptions declared to be thrown by this remote method. + * + * This list may include superfluous entries, such as + * unchecked exceptions and subclasses of other entries. + **/ + private ClassDoc[] exceptionTypes; + + /** + * Creates a new Method instance for the specified method. + **/ + Method(MethodDoc methodDoc) { + this.methodDoc = methodDoc; + exceptionTypes = methodDoc.thrownExceptions(); + /* + * Sort exception types to improve consistency with + * previous implementations. + */ + Arrays.sort(exceptionTypes, new ClassDocComparator()); + operationString = computeOperationString(); + nameAndDescriptor = + methodDoc.name() + Util.methodDescriptorOf(methodDoc); + methodHash = computeMethodHash(); + } + + /** + * Returns the MethodDoc object corresponding to this method + * of a remote interface. + **/ + MethodDoc methodDoc() { + return methodDoc; + } + + /** + * Returns the parameter types declared by this method. + **/ + Type[] parameterTypes() { + Parameter[] parameters = methodDoc.parameters(); + Type[] paramTypes = new Type[parameters.length]; + for (int i = 0; i < paramTypes.length; i++) { + paramTypes[i] = parameters[i].type(); + } + return paramTypes; + } + + /** + * Returns the exception types declared to be thrown by this + * remote method. + * + * For methods with the same name and descriptor inherited + * from multiple remote interfaces, the array will contain the + * set of exceptions declared in all of the interfaces' + * methods that can be legally thrown by all of them. + **/ + ClassDoc[] exceptionTypes() { + return (ClassDoc[]) exceptionTypes.clone(); + } + + /** + * Returns the JRMP "method hash" used to identify this remote + * method in the JDK 1.2 version of the stub protocol. + **/ + long methodHash() { + return methodHash; + } + + /** + * Returns the string representation of this method + * appropriate for the construction of a + * java.rmi.server.Operation object. + **/ + String operationString() { + return operationString; + } + + /** + * Returns a string consisting of this method's name followed + * by its descriptor. + **/ + String nameAndDescriptor() { + return nameAndDescriptor; + } + + /** + * Returns a new Method object that is a legal combination of + * this Method object and another one. + * + * Doing this requires determining the exceptions declared by + * the combined method, which must be (only) all of the + * exceptions declared in both old Methods that may thrown in + * either of them. + **/ + Method mergeWith(Method other) { + if (!nameAndDescriptor().equals(other.nameAndDescriptor())) { + throw new AssertionError( + "attempt to merge method \"" + + other.nameAndDescriptor() + "\" with \"" + + nameAndDescriptor()); + } + + List<ClassDoc> legalExceptions = new ArrayList<ClassDoc>(); + collectCompatibleExceptions( + other.exceptionTypes, exceptionTypes, legalExceptions); + collectCompatibleExceptions( + exceptionTypes, other.exceptionTypes, legalExceptions); + + Method merged = clone(); + merged.exceptionTypes = + legalExceptions.toArray(new ClassDoc[legalExceptions.size()]); + + return merged; + } + + /** + * Cloning is supported by returning a shallow copy of this + * object. + **/ + protected Method clone() { + try { + return (Method) super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError(e); + } + } + + /** + * Adds to the supplied list all exceptions in the "froms" + * array that are subclasses of an exception in the "withs" + * array. + **/ + private void collectCompatibleExceptions(ClassDoc[] froms, + ClassDoc[] withs, + List<ClassDoc> list) + { + for (ClassDoc from : froms) { + if (!list.contains(from)) { + for (ClassDoc with : withs) { + if (from.subclassOf(with)) { + list.add(from); + break; + } + } + } + } + } + + /** + * Computes the JRMP "method hash" of this remote method. The + * method hash is a long containing the first 64 bits of the + * SHA digest from the UTF-8 encoded string of the method name + * and descriptor. + **/ + private long computeMethodHash() { + long hash = 0; + ByteArrayOutputStream sink = new ByteArrayOutputStream(512); + try { + MessageDigest md = MessageDigest.getInstance("SHA"); + DataOutputStream out = new DataOutputStream( + new DigestOutputStream(sink, md)); + + String methodString = nameAndDescriptor(); + out.writeUTF(methodString); + + // use only the first 64 bits of the digest for the hash + out.flush(); + byte hashArray[] = md.digest(); + for (int i = 0; i < Math.min(8, hashArray.length); i++) { + hash += ((long) (hashArray[i] & 0xFF)) << (i * 8); + } + } catch (IOException e) { + throw new AssertionError(e); + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(e); + } + + return hash; + } + + /** + * Computes the string representation of this method + * appropriate for the construction of a + * java.rmi.server.Operation object. + **/ + private String computeOperationString() { + /* + * To be consistent with previous implementations, we use + * the deprecated style of placing the "[]" for the return + * type (if any) after the parameter list. + */ + Type returnType = methodDoc.returnType(); + String op = returnType.qualifiedTypeName() + " " + + methodDoc.name() + "("; + Parameter[] parameters = methodDoc.parameters(); + for (int i = 0; i < parameters.length; i++) { + if (i > 0) { + op += ", "; + } + op += parameters[i].type().toString(); + } + op += ")" + returnType.dimension(); + return op; + } + } +} diff --git a/src/share/classes/sun/rmi/rmic/newrmic/jrmp/StubSkeletonWriter.java b/src/share/classes/sun/rmi/rmic/newrmic/jrmp/StubSkeletonWriter.java new file mode 100644 index 000000000..0c52cfba9 --- /dev/null +++ b/src/share/classes/sun/rmi/rmic/newrmic/jrmp/StubSkeletonWriter.java @@ -0,0 +1,1079 @@ +/* + * Copyright 2003-2005 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.rmic.newrmic.jrmp; + +import com.sun.javadoc.ClassDoc; +import com.sun.javadoc.MethodDoc; +import com.sun.javadoc.Type; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import sun.rmi.rmic.newrmic.BatchEnvironment; +import sun.rmi.rmic.newrmic.IndentingWriter; + +import static sun.rmi.rmic.newrmic.Constants.*; +import static sun.rmi.rmic.newrmic.jrmp.Constants.*; + +/** + * Writes the source code for the stub class and (optionally) skeleton + * class for a particular remote implementation class. + * + * WARNING: The contents of this source file are not part of any + * supported API. Code that depends on them does so at its own risk: + * they are subject to change or removal without notice. + * + * @author Peter Jones + **/ +class StubSkeletonWriter { + + /** rmic environment for this object */ + private final BatchEnvironment env; + + /** the remote implemention class to generate code for */ + private final RemoteClass remoteClass; + + /** version of the JRMP stub protocol to generate code for */ + private final StubVersion version; + + /* + * binary names of the stub and skeleton classes to generate for + * the remote class + */ + private final String stubClassName; + private final String skeletonClassName; + + /* package name and simple names of the stub and skeleton classes */ + private final String packageName; + private final String stubClassSimpleName; + private final String skeletonClassSimpleName; + + /** remote methods of class, indexed by operation number */ + private final RemoteClass.Method[] remoteMethods; + + /** + * Names to use for the java.lang.reflect.Method static fields in + * the generated stub class corresponding to each remote method. + **/ + private final String[] methodFieldNames; + + /** + * Creates a StubSkeletonWriter instance for the specified remote + * implementation class. The generated code will implement the + * specified JRMP stub protocol version. + **/ + StubSkeletonWriter(BatchEnvironment env, + RemoteClass remoteClass, + StubVersion version) + { + this.env = env; + this.remoteClass = remoteClass; + this.version = version; + + stubClassName = Util.binaryNameOf(remoteClass.classDoc()) + "_Stub"; + skeletonClassName = + Util.binaryNameOf(remoteClass.classDoc()) + "_Skel"; + + int i = stubClassName.lastIndexOf('.'); + packageName = (i != -1 ? stubClassName.substring(0, i) : ""); + stubClassSimpleName = stubClassName.substring(i + 1); + skeletonClassSimpleName = skeletonClassName.substring(i + 1); + + remoteMethods = remoteClass.remoteMethods(); + methodFieldNames = nameMethodFields(remoteMethods); + } + + /** + * Returns the binary name of the stub class to generate for the + * remote implementation class. + **/ + String stubClassName() { + return stubClassName; + } + + /** + * Returns the binary name of the skeleton class to generate for + * the remote implementation class. + **/ + String skeletonClassName() { + return skeletonClassName; + } + + /** + * Writes the stub class for the remote class to a stream. + **/ + void writeStub(IndentingWriter p) throws IOException { + + /* + * Write boiler plate comment. + */ + p.pln("// Stub class generated by rmic, do not edit."); + p.pln("// Contents subject to change without notice."); + p.pln(); + + /* + * If remote implementation class was in a particular package, + * declare the stub class to be in the same package. + */ + if (!packageName.equals("")) { + p.pln("package " + packageName + ";"); + p.pln(); + } + + /* + * Declare the stub class; implement all remote interfaces. + */ + p.plnI("public final class " + stubClassSimpleName); + p.pln("extends " + REMOTE_STUB); + ClassDoc[] remoteInterfaces = remoteClass.remoteInterfaces(); + if (remoteInterfaces.length > 0) { + p.p("implements "); + for (int i = 0; i < remoteInterfaces.length; i++) { + if (i > 0) { + p.p(", "); + } + p.p(remoteInterfaces[i].qualifiedName()); + } + p.pln(); + } + p.pOlnI("{"); + + if (version == StubVersion.V1_1 || + version == StubVersion.VCOMPAT) + { + writeOperationsArray(p); + p.pln(); + writeInterfaceHash(p); + p.pln(); + } + + if (version == StubVersion.VCOMPAT || + version == StubVersion.V1_2) + { + p.pln("private static final long serialVersionUID = " + + STUB_SERIAL_VERSION_UID + ";"); + p.pln(); + + /* + * We only need to declare and initialize the static fields of + * Method objects for each remote method if there are any remote + * methods; otherwise, skip this code entirely, to avoid generating + * a try/catch block for a checked exception that cannot occur + * (see bugid 4125181). + */ + if (methodFieldNames.length > 0) { + if (version == StubVersion.VCOMPAT) { + p.pln("private static boolean useNewInvoke;"); + } + writeMethodFieldDeclarations(p); + p.pln(); + + /* + * Initialize java.lang.reflect.Method fields for each remote + * method in a static initializer. + */ + p.plnI("static {"); + p.plnI("try {"); + if (version == StubVersion.VCOMPAT) { + /* + * Fat stubs must determine whether the API required for + * the JDK 1.2 stub protocol is supported in the current + * runtime, so that it can use it if supported. This is + * determined by using the Reflection API to test if the + * new invoke method on RemoteRef exists, and setting the + * static boolean "useNewInvoke" to true if it does, or + * to false if a NoSuchMethodException is thrown. + */ + p.plnI(REMOTE_REF + ".class.getMethod(\"invoke\","); + p.plnI("new java.lang.Class[] {"); + p.pln(REMOTE + ".class,"); + p.pln("java.lang.reflect.Method.class,"); + p.pln("java.lang.Object[].class,"); + p.pln("long.class"); + p.pOln("});"); + p.pO(); + p.pln("useNewInvoke = true;"); + } + writeMethodFieldInitializers(p); + p.pOlnI("} catch (java.lang.NoSuchMethodException e) {"); + if (version == StubVersion.VCOMPAT) { + p.pln("useNewInvoke = false;"); + } else { + p.plnI("throw new java.lang.NoSuchMethodError("); + p.pln("\"stub class initialization failed\");"); + p.pO(); + } + p.pOln("}"); // end try/catch block + p.pOln("}"); // end static initializer + p.pln(); + } + } + + writeStubConstructors(p); + p.pln(); + + /* + * Write each stub method. + */ + if (remoteMethods.length > 0) { + p.pln("// methods from remote interfaces"); + for (int i = 0; i < remoteMethods.length; ++i) { + p.pln(); + writeStubMethod(p, i); + } + } + + p.pOln("}"); // end stub class + } + + /** + * Writes the constructors for the stub class. + **/ + private void writeStubConstructors(IndentingWriter p) + throws IOException + { + p.pln("// constructors"); + + /* + * Only stubs compatible with the JDK 1.1 stub protocol need + * a no-arg constructor; later versions use reflection to find + * the constructor that directly takes a RemoteRef argument. + */ + if (version == StubVersion.V1_1 || + version == StubVersion.VCOMPAT) + { + p.plnI("public " + stubClassSimpleName + "() {"); + p.pln("super();"); + p.pOln("}"); + } + + p.plnI("public " + stubClassSimpleName + "(" + REMOTE_REF + " ref) {"); + p.pln("super(ref);"); + p.pOln("}"); + } + + /** + * Writes the stub method for the remote method with the given + * operation number. + **/ + private void writeStubMethod(IndentingWriter p, int opnum) + throws IOException + { + RemoteClass.Method method = remoteMethods[opnum]; + MethodDoc methodDoc = method.methodDoc(); + String methodName = methodDoc.name(); + Type[] paramTypes = method.parameterTypes(); + String paramNames[] = nameParameters(paramTypes); + Type returnType = methodDoc.returnType(); + ClassDoc[] exceptions = method.exceptionTypes(); + + /* + * Declare stub method; throw exceptions declared in remote + * interface(s). + */ + p.pln("// implementation of " + + Util.getFriendlyUnqualifiedSignature(methodDoc)); + p.p("public " + returnType.toString() + " " + methodName + "("); + for (int i = 0; i < paramTypes.length; i++) { + if (i > 0) { + p.p(", "); + } + p.p(paramTypes[i].toString() + " " + paramNames[i]); + } + p.plnI(")"); + if (exceptions.length > 0) { + p.p("throws "); + for (int i = 0; i < exceptions.length; i++) { + if (i > 0) { + p.p(", "); + } + p.p(exceptions[i].qualifiedName()); + } + p.pln(); + } + p.pOlnI("{"); + + /* + * The RemoteRef.invoke methods throw Exception, but unless + * this stub method throws Exception as well, we must catch + * Exceptions thrown from the invocation. So we must catch + * Exception and rethrow something we can throw: + * UnexpectedException, which is a subclass of + * RemoteException. But for any subclasses of Exception that + * we can throw, like RemoteException, RuntimeException, and + * any of the exceptions declared by this stub method, we want + * them to pass through unmodified, so first we must catch any + * such exceptions and rethrow them directly. + * + * We have to be careful generating the rethrowing catch + * blocks here, because javac will flag an error if there are + * any unreachable catch blocks, i.e. if the catch of an + * exception class follows a previous catch of it or of one of + * its superclasses. The following method invocation takes + * care of these details. + */ + List<ClassDoc> catchList = computeUniqueCatchList(exceptions); + + /* + * If we need to catch any particular exceptions (i.e. this method + * does not declare java.lang.Exception), put the entire stub + * method in a try block. + */ + if (catchList.size() > 0) { + p.plnI("try {"); + } + + if (version == StubVersion.VCOMPAT) { + p.plnI("if (useNewInvoke) {"); + } + if (version == StubVersion.VCOMPAT || + version == StubVersion.V1_2) + { + if (!Util.isVoid(returnType)) { + p.p("Object $result = "); // REMIND: why $? + } + p.p("ref.invoke(this, " + methodFieldNames[opnum] + ", "); + if (paramTypes.length > 0) { + p.p("new java.lang.Object[] {"); + for (int i = 0; i < paramTypes.length; i++) { + if (i > 0) + p.p(", "); + p.p(wrapArgumentCode(paramTypes[i], paramNames[i])); + } + p.p("}"); + } else { + p.p("null"); + } + p.pln(", " + method.methodHash() + "L);"); + if (!Util.isVoid(returnType)) { + p.pln("return " + + unwrapArgumentCode(returnType, "$result") + ";"); + } + } + if (version == StubVersion.VCOMPAT) { + p.pOlnI("} else {"); + } + if (version == StubVersion.V1_1 || + version == StubVersion.VCOMPAT) + { + p.pln(REMOTE_CALL + " call = ref.newCall((" + REMOTE_OBJECT + + ") this, operations, " + opnum + ", interfaceHash);"); + + if (paramTypes.length > 0) { + p.plnI("try {"); + p.pln("java.io.ObjectOutput out = call.getOutputStream();"); + writeMarshalArguments(p, "out", paramTypes, paramNames); + p.pOlnI("} catch (java.io.IOException e) {"); + p.pln("throw new " + MARSHAL_EXCEPTION + + "(\"error marshalling arguments\", e);"); + p.pOln("}"); + } + + p.pln("ref.invoke(call);"); + + if (Util.isVoid(returnType)) { + p.pln("ref.done(call);"); + } else { + p.pln(returnType.toString() + " $result;"); + // REMIND: why $? + p.plnI("try {"); + p.pln("java.io.ObjectInput in = call.getInputStream();"); + boolean objectRead = + writeUnmarshalArgument(p, "in", returnType, "$result"); + p.pln(";"); + p.pOlnI("} catch (java.io.IOException e) {"); + p.pln("throw new " + UNMARSHAL_EXCEPTION + + "(\"error unmarshalling return\", e);"); + /* + * If any only if readObject has been invoked, we must catch + * ClassNotFoundException as well as IOException. + */ + if (objectRead) { + p.pOlnI("} catch (java.lang.ClassNotFoundException e) {"); + p.pln("throw new " + UNMARSHAL_EXCEPTION + + "(\"error unmarshalling return\", e);"); + } + p.pOlnI("} finally {"); + p.pln("ref.done(call);"); + p.pOln("}"); + p.pln("return $result;"); + } + } + if (version == StubVersion.VCOMPAT) { + p.pOln("}"); // end if/else (useNewInvoke) block + } + + /* + * If we need to catch any particular exceptions, finally write + * the catch blocks for them, rethrow any other Exceptions with an + * UnexpectedException, and end the try block. + */ + if (catchList.size() > 0) { + for (ClassDoc catchClass : catchList) { + p.pOlnI("} catch (" + catchClass.qualifiedName() + " e) {"); + p.pln("throw e;"); + } + p.pOlnI("} catch (java.lang.Exception e) {"); + p.pln("throw new " + UNEXPECTED_EXCEPTION + + "(\"undeclared checked exception\", e);"); + p.pOln("}"); // end try/catch block + } + + p.pOln("}"); // end stub method + } + + /** + * Computes the exceptions that need to be caught and rethrown in + * a stub method before wrapping Exceptions in + * UnexpectedExceptions, given the exceptions declared in the + * throws clause of the method. Returns a list containing the + * exception to catch. Each exception is guaranteed to be unique, + * i.e. not a subclass of any of the other exceptions in the list, + * so the catch blocks for these exceptions may be generated in + * any order relative to each other. + * + * RemoteException and RuntimeException are each automatically + * placed in the returned list (unless any of their superclasses + * are already present), since those exceptions should always be + * directly rethrown by a stub method. + * + * The returned list will be empty if java.lang.Exception or one + * of its superclasses is in the throws clause of the method, + * indicating that no exceptions need to be caught. + **/ + private List<ClassDoc> computeUniqueCatchList(ClassDoc[] exceptions) { + List<ClassDoc> uniqueList = new ArrayList<ClassDoc>(); + + uniqueList.add(env.docRuntimeException()); + uniqueList.add(env.docRemoteException()); // always catch/rethrow these + + /* For each exception declared by the stub method's throws clause: */ + nextException: + for (ClassDoc ex : exceptions) { + if (env.docException().subclassOf(ex)) { + /* + * If java.lang.Exception (or a superclass) was declared + * in the throws clause of this stub method, then we don't + * have to bother catching anything; clear the list and + * return. + */ + uniqueList.clear(); + break; + } else if (!ex.subclassOf(env.docException())) { + /* + * Ignore other Throwables that do not extend Exception, + * because they cannot be thrown by the invoke methods. + */ + continue; + } + /* + * Compare this exception against the current list of + * exceptions that need to be caught: + */ + for (Iterator<ClassDoc> i = uniqueList.iterator(); i.hasNext();) { + ClassDoc ex2 = i.next(); + if (ex.subclassOf(ex2)) { + /* + * If a superclass of this exception is already on + * the list to catch, then ignore this one and continue; + */ + continue nextException; + } else if (ex2.subclassOf(ex)) { + /* + * If a subclass of this exception is on the list + * to catch, then remove it; + */ + i.remove(); + } + } + /* This exception is unique: add it to the list to catch. */ + uniqueList.add(ex); + } + return uniqueList; + } + + /** + * Writes the skeleton for the remote class to a stream. + **/ + void writeSkeleton(IndentingWriter p) throws IOException { + if (version == StubVersion.V1_2) { + throw new AssertionError( + "should not generate skeleton for version " + version); + } + + /* + * Write boiler plate comment. + */ + p.pln("// Skeleton class generated by rmic, do not edit."); + p.pln("// Contents subject to change without notice."); + p.pln(); + + /* + * If remote implementation class was in a particular package, + * declare the skeleton class to be in the same package. + */ + if (!packageName.equals("")) { + p.pln("package " + packageName + ";"); + p.pln(); + } + + /* + * Declare the skeleton class. + */ + p.plnI("public final class " + skeletonClassSimpleName); + p.pln("implements " + SKELETON); + p.pOlnI("{"); + + writeOperationsArray(p); + p.pln(); + + writeInterfaceHash(p); + p.pln(); + + /* + * Define the getOperations() method. + */ + p.plnI("public " + OPERATION + "[] getOperations() {"); + p.pln("return (" + OPERATION + "[]) operations.clone();"); + p.pOln("}"); + p.pln(); + + /* + * Define the dispatch() method. + */ + p.plnI("public void dispatch(" + REMOTE + " obj, " + + REMOTE_CALL + " call, int opnum, long hash)"); + p.pln("throws java.lang.Exception"); + p.pOlnI("{"); + + if (version == StubVersion.VCOMPAT) { + p.plnI("if (opnum < 0) {"); + if (remoteMethods.length > 0) { + for (int opnum = 0; opnum < remoteMethods.length; opnum++) { + if (opnum > 0) + p.pO("} else "); + p.plnI("if (hash == " + + remoteMethods[opnum].methodHash() + "L) {"); + p.pln("opnum = " + opnum + ";"); + } + p.pOlnI("} else {"); + } + /* + * Skeleton throws UnmarshalException if it does not recognize + * the method hash; this is what UnicastServerRef.dispatch() + * would do. + */ + p.pln("throw new " + + UNMARSHAL_EXCEPTION + "(\"invalid method hash\");"); + if (remoteMethods.length > 0) { + p.pOln("}"); + } + /* + * Ignore the validation of the interface hash if the + * operation number was negative, since it is really a + * method hash instead. + */ + p.pOlnI("} else {"); + } + + p.plnI("if (hash != interfaceHash)"); + p.pln("throw new " + + SKELETON_MISMATCH_EXCEPTION + "(\"interface hash mismatch\");"); + p.pO(); + + if (version == StubVersion.VCOMPAT) { + p.pOln("}"); // end if/else (opnum < 0) block + } + p.pln(); + + /* + * Cast remote object reference to the remote implementation + * class, if it's not private. We don't use the binary name + * of the class like previous implementations did because that + * would not compile with javac (since 1.4.1). If the remote + * implementation class is private, then we can't cast to it + * like previous implementations did because that also would + * not compile with javac-- so instead, we'll have to try to + * cast to the remote interface for each remote method. + */ + if (!remoteClass.classDoc().isPrivate()) { + p.pln(remoteClass.classDoc().qualifiedName() + " server = (" + + remoteClass.classDoc().qualifiedName() + ") obj;"); + } + + /* + * Process call according to the operation number. + */ + p.plnI("switch (opnum) {"); + for (int opnum = 0; opnum < remoteMethods.length; opnum++) { + writeSkeletonDispatchCase(p, opnum); + } + p.pOlnI("default:"); + /* + * Skeleton throws UnmarshalException if it does not recognize + * the operation number; this is consistent with the case of an + * unrecognized method hash. + */ + p.pln("throw new " + UNMARSHAL_EXCEPTION + + "(\"invalid method number\");"); + p.pOln("}"); // end switch statement + + p.pOln("}"); // end dispatch() method + + p.pOln("}"); // end skeleton class + } + + /** + * Writes the case block for the skeleton's dispatch method for + * the remote method with the given "opnum". + **/ + private void writeSkeletonDispatchCase(IndentingWriter p, int opnum) + throws IOException + { + RemoteClass.Method method = remoteMethods[opnum]; + MethodDoc methodDoc = method.methodDoc(); + String methodName = methodDoc.name(); + Type paramTypes[] = method.parameterTypes(); + String paramNames[] = nameParameters(paramTypes); + Type returnType = methodDoc.returnType(); + + p.pOlnI("case " + opnum + ": // " + + Util.getFriendlyUnqualifiedSignature(methodDoc)); + /* + * Use nested block statement inside case to provide an independent + * namespace for local variables used to unmarshal parameters for + * this remote method. + */ + p.pOlnI("{"); + + if (paramTypes.length > 0) { + /* + * Declare local variables to hold arguments. + */ + for (int i = 0; i < paramTypes.length; i++) { + p.pln(paramTypes[i].toString() + " " + paramNames[i] + ";"); + } + + /* + * Unmarshal arguments from call stream. + */ + p.plnI("try {"); + p.pln("java.io.ObjectInput in = call.getInputStream();"); + boolean objectsRead = writeUnmarshalArguments(p, "in", + paramTypes, paramNames); + p.pOlnI("} catch (java.io.IOException e) {"); + p.pln("throw new " + UNMARSHAL_EXCEPTION + + "(\"error unmarshalling arguments\", e);"); + /* + * If any only if readObject has been invoked, we must catch + * ClassNotFoundException as well as IOException. + */ + if (objectsRead) { + p.pOlnI("} catch (java.lang.ClassNotFoundException e) {"); + p.pln("throw new " + UNMARSHAL_EXCEPTION + + "(\"error unmarshalling arguments\", e);"); + } + p.pOlnI("} finally {"); + p.pln("call.releaseInputStream();"); + p.pOln("}"); + } else { + p.pln("call.releaseInputStream();"); + } + + if (!Util.isVoid(returnType)) { + /* + * Declare variable to hold return type, if not void. + */ + p.p(returnType.toString() + " $result = "); + // REMIND: why $? + } + + /* + * Invoke the method on the server object. If the remote + * implementation class is private, then we don't have a + * reference cast to it, and so we try to cast to the remote + * object reference to the method's declaring interface here. + */ + String target = remoteClass.classDoc().isPrivate() ? + "((" + methodDoc.containingClass().qualifiedName() + ") obj)" : + "server"; + p.p(target + "." + methodName + "("); + for (int i = 0; i < paramNames.length; i++) { + if (i > 0) + p.p(", "); + p.p(paramNames[i]); + } + p.pln(");"); + + /* + * Always invoke getResultStream(true) on the call object to send + * the indication of a successful invocation to the caller. If + * the return type is not void, keep the result stream and marshal + * the return value. + */ + p.plnI("try {"); + if (!Util.isVoid(returnType)) { + p.p("java.io.ObjectOutput out = "); + } + p.pln("call.getResultStream(true);"); + if (!Util.isVoid(returnType)) { + writeMarshalArgument(p, "out", returnType, "$result"); + p.pln(";"); + } + p.pOlnI("} catch (java.io.IOException e) {"); + p.pln("throw new " + + MARSHAL_EXCEPTION + "(\"error marshalling return\", e);"); + p.pOln("}"); + + p.pln("break;"); // break from switch statement + + p.pOlnI("}"); // end nested block statement + p.pln(); + } + + /** + * Writes declaration and initializer for "operations" static array. + **/ + private void writeOperationsArray(IndentingWriter p) + throws IOException + { + p.plnI("private static final " + OPERATION + "[] operations = {"); + for (int i = 0; i < remoteMethods.length; i++) { + if (i > 0) + p.pln(","); + p.p("new " + OPERATION + "(\"" + + remoteMethods[i].operationString() + "\")"); + } + p.pln(); + p.pOln("};"); + } + + /** + * Writes declaration and initializer for "interfaceHash" static field. + **/ + private void writeInterfaceHash(IndentingWriter p) + throws IOException + { + p.pln("private static final long interfaceHash = " + + remoteClass.interfaceHash() + "L;"); + } + + /** + * Writes declaration for java.lang.reflect.Method static fields + * corresponding to each remote method in a stub. + **/ + private void writeMethodFieldDeclarations(IndentingWriter p) + throws IOException + { + for (String name : methodFieldNames) { + p.pln("private static java.lang.reflect.Method " + name + ";"); + } + } + + /** + * Writes code to initialize the static fields for each method + * using the Java Reflection API. + **/ + private void writeMethodFieldInitializers(IndentingWriter p) + throws IOException + { + for (int i = 0; i < methodFieldNames.length; i++) { + p.p(methodFieldNames[i] + " = "); + /* + * Look up the Method object in the somewhat arbitrary + * interface that we find in the Method object. + */ + RemoteClass.Method method = remoteMethods[i]; + MethodDoc methodDoc = method.methodDoc(); + String methodName = methodDoc.name(); + Type paramTypes[] = method.parameterTypes(); + + p.p(methodDoc.containingClass().qualifiedName() + ".class.getMethod(\"" + + methodName + "\", new java.lang.Class[] {"); + for (int j = 0; j < paramTypes.length; j++) { + if (j > 0) + p.p(", "); + p.p(paramTypes[j].toString() + ".class"); + } + p.pln("});"); + } + } + + + /* + * Following are a series of static utility methods useful during + * the code generation process: + */ + + /** + * Generates an array of names for fields correspondins to the + * given array of remote methods. Each name in the returned array + * is guaranteed to be unique. + * + * The name of a method is included in its corresponding field + * name to enhance readability of the generated code. + **/ + private static String[] nameMethodFields(RemoteClass.Method[] methods) { + String[] names = new String[methods.length]; + for (int i = 0; i < names.length; i++) { + names[i] = "$method_" + methods[i].methodDoc().name() + "_" + i; + } + return names; + } + + /** + * Generates an array of names for parameters corresponding to the + * given array of types for the parameters. Each name in the + * returned array is guaranteed to be unique. + * + * A representation of the type of a parameter is included in its + * corresponding parameter name to enhance the readability of the + * generated code. + **/ + private static String[] nameParameters(Type[] types) { + String[] names = new String[types.length]; + for (int i = 0; i < names.length; i++) { + names[i] = "$param_" + + generateNameFromType(types[i]) + "_" + (i + 1); + } + return names; + } + + /** + * Generates a readable string representing the given type + * suitable for embedding within a Java identifier. + **/ + private static String generateNameFromType(Type type) { + String name = type.typeName().replace('.', '$'); + int dimensions = type.dimension().length() / 2; + for (int i = 0; i < dimensions; i++) { + name = "arrayOf_" + name; + } + return name; + } + + /** + * Writes a snippet of Java code to marshal a value named "name" + * of type "type" to the java.io.ObjectOutput stream named + * "stream". + * + * Primitive types are marshalled with their corresponding methods + * in the java.io.DataOutput interface, and objects (including + * arrays) are marshalled using the writeObject method. + **/ + private static void writeMarshalArgument(IndentingWriter p, + String streamName, + Type type, String name) + throws IOException + { + if (type.dimension().length() > 0 || type.asClassDoc() != null) { + p.p(streamName + ".writeObject(" + name + ")"); + } else if (type.typeName().equals("boolean")) { + p.p(streamName + ".writeBoolean(" + name + ")"); + } else if (type.typeName().equals("byte")) { + p.p(streamName + ".writeByte(" + name + ")"); + } else if (type.typeName().equals("char")) { + p.p(streamName + ".writeChar(" + name + ")"); + } else if (type.typeName().equals("short")) { + p.p(streamName + ".writeShort(" + name + ")"); + } else if (type.typeName().equals("int")) { + p.p(streamName + ".writeInt(" + name + ")"); + } else if (type.typeName().equals("long")) { + p.p(streamName + ".writeLong(" + name + ")"); + } else if (type.typeName().equals("float")) { + p.p(streamName + ".writeFloat(" + name + ")"); + } else if (type.typeName().equals("double")) { + p.p(streamName + ".writeDouble(" + name + ")"); + } else { + throw new AssertionError(type); + } + } + + /** + * Writes Java statements to marshal a series of values in order + * as named in the "names" array, with types as specified in the + * "types" array, to the java.io.ObjectOutput stream named + * "stream". + **/ + private static void writeMarshalArguments(IndentingWriter p, + String streamName, + Type[] types, String[] names) + throws IOException + { + assert types.length == names.length; + + for (int i = 0; i < types.length; i++) { + writeMarshalArgument(p, streamName, types[i], names[i]); + p.pln(";"); + } + } + + /** + * Writes a snippet of Java code to unmarshal a value of type + * "type" from the java.io.ObjectInput stream named "stream" into + * a variable named "name" (if "name" is null, the value is + * unmarshalled and discarded). + * + * Primitive types are unmarshalled with their corresponding + * methods in the java.io.DataInput interface, and objects + * (including arrays) are unmarshalled using the readObject + * method. + * + * Returns true if code to invoke readObject was written, and + * false otherwise. + **/ + private static boolean writeUnmarshalArgument(IndentingWriter p, + String streamName, + Type type, String name) + throws IOException + { + boolean readObject = false; + + if (name != null) { + p.p(name + " = "); + } + + if (type.dimension().length() > 0 || type.asClassDoc() != null) { + p.p("(" + type.toString() + ") " + streamName + ".readObject()"); + readObject = true; + } else if (type.typeName().equals("boolean")) { + p.p(streamName + ".readBoolean()"); + } else if (type.typeName().equals("byte")) { + p.p(streamName + ".readByte()"); + } else if (type.typeName().equals("char")) { + p.p(streamName + ".readChar()"); + } else if (type.typeName().equals("short")) { + p.p(streamName + ".readShort()"); + } else if (type.typeName().equals("int")) { + p.p(streamName + ".readInt()"); + } else if (type.typeName().equals("long")) { + p.p(streamName + ".readLong()"); + } else if (type.typeName().equals("float")) { + p.p(streamName + ".readFloat()"); + } else if (type.typeName().equals("double")) { + p.p(streamName + ".readDouble()"); + } else { + throw new AssertionError(type); + } + + return readObject; + } + + /** + * Writes Java statements to unmarshal a series of values in order + * of types as in the "types" array from the java.io.ObjectInput + * stream named "stream" into variables as named in "names" (for + * any element of "names" that is null, the corresponding value is + * unmarshalled and discarded). + **/ + private static boolean writeUnmarshalArguments(IndentingWriter p, + String streamName, + Type[] types, + String[] names) + throws IOException + { + assert types.length == names.length; + + boolean readObject = false; + for (int i = 0; i < types.length; i++) { + if (writeUnmarshalArgument(p, streamName, types[i], names[i])) { + readObject = true; + } + p.pln(";"); + } + return readObject; + } + + /** + * Returns a snippet of Java code to wrap a value named "name" of + * type "type" into an object as appropriate for use by the Java + * Reflection API. + * + * For primitive types, an appropriate wrapper class is + * instantiated with the primitive value. For object types + * (including arrays), no wrapping is necessary, so the value is + * named directly. + **/ + private static String wrapArgumentCode(Type type, String name) { + if (type.dimension().length() > 0 || type.asClassDoc() != null) { + return name; + } else if (type.typeName().equals("boolean")) { + return ("(" + name + + " ? java.lang.Boolean.TRUE : java.lang.Boolean.FALSE)"); + } else if (type.typeName().equals("byte")) { + return "new java.lang.Byte(" + name + ")"; + } else if (type.typeName().equals("char")) { + return "new java.lang.Character(" + name + ")"; + } else if (type.typeName().equals("short")) { + return "new java.lang.Short(" + name + ")"; + } else if (type.typeName().equals("int")) { + return "new java.lang.Integer(" + name + ")"; + } else if (type.typeName().equals("long")) { + return "new java.lang.Long(" + name + ")"; + } else if (type.typeName().equals("float")) { + return "new java.lang.Float(" + name + ")"; + } else if (type.typeName().equals("double")) { + return "new java.lang.Double(" + name + ")"; + } else { + throw new AssertionError(type); + } + } + + /** + * Returns a snippet of Java code to unwrap a value named "name" + * into a value of type "type", as appropriate for the Java + * Reflection API. + * + * For primitive types, the value is assumed to be of the + * corresponding wrapper class, and a method is called on the + * wrapper to retrieve the primitive value. For object types + * (include arrays), no unwrapping is necessary; the value is + * simply cast to the expected real object type. + **/ + private static String unwrapArgumentCode(Type type, String name) { + if (type.dimension().length() > 0 || type.asClassDoc() != null) { + return "((" + type.toString() + ") " + name + ")"; + } else if (type.typeName().equals("boolean")) { + return "((java.lang.Boolean) " + name + ").booleanValue()"; + } else if (type.typeName().equals("byte")) { + return "((java.lang.Byte) " + name + ").byteValue()"; + } else if (type.typeName().equals("char")) { + return "((java.lang.Character) " + name + ").charValue()"; + } else if (type.typeName().equals("short")) { + return "((java.lang.Short) " + name + ").shortValue()"; + } else if (type.typeName().equals("int")) { + return "((java.lang.Integer) " + name + ").intValue()"; + } else if (type.typeName().equals("long")) { + return "((java.lang.Long) " + name + ").longValue()"; + } else if (type.typeName().equals("float")) { + return "((java.lang.Float) " + name + ").floatValue()"; + } else if (type.typeName().equals("double")) { + return "((java.lang.Double) " + name + ").doubleValue()"; + } else { + throw new AssertionError(type); + } + } +} diff --git a/src/share/classes/sun/rmi/rmic/newrmic/jrmp/Util.java b/src/share/classes/sun/rmi/rmic/newrmic/jrmp/Util.java new file mode 100644 index 000000000..a5e5f6926 --- /dev/null +++ b/src/share/classes/sun/rmi/rmic/newrmic/jrmp/Util.java @@ -0,0 +1,149 @@ +/* + * Copyright 2003 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.rmic.newrmic.jrmp; + +import com.sun.javadoc.ClassDoc; +import com.sun.javadoc.MethodDoc; +import com.sun.javadoc.Parameter; +import com.sun.javadoc.Type; + +/** + * Provides static utility methods. + * + * WARNING: The contents of this source file are not part of any + * supported API. Code that depends on them does so at its own risk: + * they are subject to change or removal without notice. + * + * @author Peter Jones + **/ +final class Util { + + private Util() { throw new AssertionError(); } + + /** + * Returns the binary name of the class or interface represented + * by the specified ClassDoc. + **/ + static String binaryNameOf(ClassDoc cl) { + String flat = cl.name().replace('.', '$'); + String packageName = cl.containingPackage().name(); + return packageName.equals("") ? flat : packageName + "." + flat; + } + + /** + * Returns the method descriptor for the specified method. + * + * See section 4.3.3 of The Java Virtual Machine Specification + * Second Edition for the definition of a "method descriptor". + **/ + static String methodDescriptorOf(MethodDoc method) { + String desc = "("; + Parameter[] parameters = method.parameters(); + for (int i = 0; i < parameters.length; i++) { + desc += typeDescriptorOf(parameters[i].type()); + } + desc += ")" + typeDescriptorOf(method.returnType()); + return desc; + } + + /** + * Returns the descriptor for the specified type, as appropriate + * for either a parameter or return type in a method descriptor. + **/ + private static String typeDescriptorOf(Type type) { + String desc; + ClassDoc classDoc = type.asClassDoc(); + if (classDoc == null) { + /* + * Handle primitive types. + */ + String name = type.typeName(); + if (name.equals("boolean")) { + desc = "Z"; + } else if (name.equals("byte")) { + desc = "B"; + } else if (name.equals("char")) { + desc = "C"; + } else if (name.equals("short")) { + desc = "S"; + } else if (name.equals("int")) { + desc = "I"; + } else if (name.equals("long")) { + desc = "J"; + } else if (name.equals("float")) { + desc = "F"; + } else if (name.equals("double")) { + desc = "D"; + } else if (name.equals("void")) { + desc = "V"; + } else { + throw new AssertionError( + "unrecognized primitive type: " + name); + } + } else { + /* + * Handle non-array reference types. + */ + desc = "L" + binaryNameOf(classDoc).replace('.', '/') + ";"; + } + + /* + * Handle array types. + */ + int dimensions = type.dimension().length() / 2; + for (int i = 0; i < dimensions; i++) { + desc = "[" + desc; + } + + return desc; + } + + /** + * Returns a reader-friendly string representation of the + * specified method's signature. Names of reference types are not + * package-qualified. + **/ + static String getFriendlyUnqualifiedSignature(MethodDoc method) { + String sig = method.name() + "("; + Parameter[] parameters = method.parameters(); + for (int i = 0; i < parameters.length; i++) { + if (i > 0) { + sig += ", "; + } + Type paramType = parameters[i].type(); + sig += paramType.typeName() + paramType.dimension(); + } + sig += ")"; + return sig; + } + + /** + * Returns true if the specified type is void. + **/ + static boolean isVoid(Type type) { + return type.asClassDoc() == null && type.typeName().equals("void"); + } +} diff --git a/src/share/classes/sun/rmi/rmic/resources/rmic.properties b/src/share/classes/sun/rmi/rmic/resources/rmic.properties new file mode 100644 index 000000000..662ffbf5d --- /dev/null +++ b/src/share/classes/sun/rmi/rmic/resources/rmic.properties @@ -0,0 +1,222 @@ +# +# +# Copyright 1996-2007 Sun Microsystems, Inc. All Rights Reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Sun designates this +# particular file as subject to the "Classpath" exception as provided +# by Sun in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, +# CA 95054 USA or visit www.sun.com if you need additional information or +# have any questions. +# + + +#***************************************************************************** +#* Copyright (c) IBM Corporation 1998 * +#* * +#* (C) Copyright IBM Corp. 1998 * +#* * +#***************************************************************************** + +# To add a generator sun.rmi.rmic.Foo which is invoked via the -foo option: +# +# 1. Add "foo" to generator.args list. +# 2. Add line: generator.class.foo=sun.rmi.rmic.Foo +# 3. Update rmic.usage string to include new arguments. + +# For each available generator, list the command line argument used +# to invoke it. The value can be a single item or a comma separated +# list. + +generator.args=v1.1,vcompat,v1.2,iiop,idl,xprint + +# For each generator, specify the class to invoke, using the following +# syntax: +# +# generator.class.{arg}=fullClassName +# +# The 'default' entry is required and will be used if none of the args +# specified in generator.args is passed. Note that {arg} is compared +# using String.equalsIgnoreCase(). + +generator.class.default=sun.rmi.rmic.RMIGenerator + +generator.class.v1.1=sun.rmi.rmic.RMIGenerator +generator.class.vcompat=sun.rmi.rmic.RMIGenerator +generator.class.v1.2=sun.rmi.rmic.RMIGenerator +generator.class.iiop=sun.rmi.rmic.iiop.StubGenerator +generator.class.idl=sun.rmi.rmic.iiop.IDLGenerator +generator.class.xprint=sun.rmi.rmic.iiop.PrintGenerator + +# If a generator needs a BatchEnvironment other than +# sun.rmi.rmic.BatchEnvironment, specify it as follows: +# +# generator.env.{arg}=fullClassName + +generator.env.iiop=sun.rmi.rmic.iiop.BatchEnvironment +generator.env.idl=sun.rmi.rmic.iiop.BatchEnvironment +generator.env.xprint=sun.rmi.rmic.iiop.BatchEnvironment + +rmic.usage=Usage: {0} <options> <class names>\ +\n\ +\nwhere <options> includes:\ +\n -keep Do not delete intermediate generated source files\ +\n -keepgenerated (same as "-keep")\ +\n -v1.1 Create stubs/skeletons for 1.1 stub protocol version\ +\n -vcompat Create stubs/skeletons compatible with both\ +\n 1.1 and 1.2 stub protocol versions\ +\n -v1.2 (default) Create stubs for 1.2 stub protocol version only\ +\n -iiop Create stubs for IIOP. When present, <options> also includes:\ +\n\ +\n -always Create stubs even when they appear current\ +\n -alwaysgenerate (same as "-always")\ +\n -nolocalstubs Do not create stubs optimized for same process\ +\n\ +\n -idl Create IDL. When present, <options> also includes:\ +\n\ +\n -noValueMethods Do not generate methods for valuetypes \ +\n -always Create IDL even when it appears current\ +\n -alwaysgenerate (same as "-always")\ +\n\ +\n -g Generate debugging info\ +\n -nowarn Generate no warnings\ +\n -nowrite Do not write compiled classes to the file system\ +\n -verbose Output messages about what the compiler is doing\ +\n -classpath <path> Specify where to find input class files\ +\n -bootclasspath <path> Override location of bootstrap class files\ +\n -extdirs <path> Override location of installed extensions\ +\n -d <directory> Specify where to place generated class files\ +\n -J<runtime flag> Pass argument to the java interpreter +\n\ + +# +# Generic Messages +# + +rmic.cant.read=Can''t read: {0} +rmic.cant.write=Can''t write: {0} +rmic.option.unsupported=The {0} option is no longer supported. +rmic.option.unimplemented=The {0} option is not yet implemented. +rmic.option.already.seen=The {0} option may be specified no more than once. +rmic.option.requires.argument=The {0} option requires an argument. +rmic.no.such.directory=The {0} directory does not exist. +rmic.no.such.option={0} is an invalid option or argument. +rmic.wrote=[wrote {0}] +rmic.errors={0} errors +rmic.1error=1 error +rmic.warnings={0} warnings +rmic.1warning=1 warning +rmic.done_in=[done in {0} ms] +rmic.no.memory=\ + The compiler has run out of memory. Consider using the "-J-Xmx<size>" command line option to increase the maximum heap size. +rmic.stack.overflow=\ + The compiler has run out of stack space. Consider using the "-J-Xss<size>" command line option to increase the memory allocated for the Java stack. +rmic.class.not.found=\ + Class {0} not found. +rmic.missing.property=Missing property generator.class.{0} +rmic.cannot.instantiate=Cannot instantiate class {0} +rmic.cannot.use.both=Cannot use both {0} and {1} +rmic.resource.not.found={0} not found. +rmic.no.output.dir=\ + Cannot find suitable output directory for {0}. Use the -d option to specify a root directory. +rmic.cannot.create.dir=\ + Cannot create output directory {0}. + +# +# JRMP Messages +# + +rmic.cant.make.stubs.for.interface=\ + {0} is an interface; stubs are needed only for remote object classes. +rmic.must.implement.remote=\ + Class {0} does not implement an interface that extends java.rmi.Remote; only remote objects need stubs and skeletons. +rmic.must.implement.remote.directly=\ + Stubs are only needed for classes that directly implement an interface that extends java.rmi.Remote; class {0} does not directly implement a remote interface. +rmic.must.throw.remoteexception=\ + {0} is not a valid remote interface: method {1} must throw java.rmi.RemoteException. +rmic.must.only.throw.exception=\ + Method {0} is not a valid remote method implementation because it throws {1}; implementations of remote methods may only throw java.lang.Exception or its subclasses. +warn.rmic.tie.found=\ + An IIOP "tie" exists for class {0}:\ + \n {1}\ + \nIf you use PortableRemoteObject.exportObject, you should remove this file; otherwise, your server object will be exported to IIOP rather than to JRMP. + +# +# RMI-IIOP Messages +# + +rmic.generated=[generated {0} in {1} ms] +rmic.previously.generated=[previously generated file {0} is current] +warn.rmic.member.not.mapped=\ + Data member {0} of class {1} was not mapped to IDL. + +rmic.iiop.constraint.1=\ + {0} is not a valid interface: does not inherit from java.rmi.Remote. +rmic.iiop.constraint.2=\ + serialPersistentFields array of class {0} is invalid: references non-existent members. +rmic.iiop.constraint.3=\ + {0} is not a valid remote interface: {1} is not a valid primitive or String constant. +rmic.iiop.constraint.4=\ + {0} is not a valid value: serialPersistentFields must be private static final. +rmic.iiop.constraint.5=\ + {0} is not a valid remote interface: method {1} must throw RemoteException or a superclass of RemoteException. +rmic.iiop.constraint.6=\ + {0} is not a valid remote interface: inherited interfaces {1} both declare method {2}. +rmic.iiop.constraint.7=\ + {0} is not a valid type: {1} differ only in case. +rmic.iiop.constraint.8=\ + {0} is not a valid remote implementation: has no remote interfaces. +rmic.iiop.constraint.9=\ + serialPersistentFields array member {0} of class {1} is invalid: type does not match declared member. +rmic.iiop.constraint.10=\ + {0} is not a valid value: implements java.rmi.Remote. +rmic.iiop.constraint.11=\ + {0} is not a valid value: does not implement java.io.Serializable. +rmic.iiop.constraint.12=\ + {0} is not a valid value: invalid parent. +rmic.iiop.constraint.13=\ + {0} is not a valid interface: the idl name for method {1} conflicts with another method. +rmic.iiop.constraint.14=\ + {0} is not a valid abstract interface: not an interface. +rmic.iiop.constraint.15=\ + {0} is not a valid abstract interface: implements java.rmi.Remote. +rmic.iiop.constraint.16=\ + {0} is not a valid remote interface: not an interface. +rmic.iiop.constraint.17=\ + {0} is not a valid remote implementation: not a class. +rmic.iiop.constraint.18=\ + {0} is not a valid interface: method {1} may not pass an exception which implements org.omg.CORBA.portable.IDLEntity. +rmic.iiop.constraint.19=\ + {0} is not a valid interface: the idl name for constant {1} conflicts with another constant. +rmic.iiop.constraint.20=\ + {0} is not a valid class: the idl name for member {1} conflicts with another member. +rmic.iiop.constraint.21=\ + {0} is a remote implementation class and cannot be used as a method argument or return type in {1}. +rmic.iiop.constraint.22=\ + Internal failure: (Method) exception {0} not a class type. +rmic.iiop.constraint.23=\ + Internal failure: (Method) caught null pointer exception for {0}. +rmic.iiop.constraint.24=\ + Class {0} contains an invalid return type. +rmic.iiop.constraint.25=\ + Class {0} contains an invalid argument type in method {1}. +rmic.iiop.constraint.26=\ + Could not compile {0}. +rmic.iiop.constraint.27=\ + Could not load class {0}. +rmic.iiop.constraint.28=\ + {0} is a remote implementation class and cannot be used as a data member in {1}. diff --git a/src/share/classes/sun/rmi/rmic/resources/rmic_ja.properties b/src/share/classes/sun/rmi/rmic/resources/rmic_ja.properties new file mode 100644 index 000000000..22d51a82d --- /dev/null +++ b/src/share/classes/sun/rmi/rmic/resources/rmic_ja.properties @@ -0,0 +1,221 @@ +# +# +# Copyright 1999-2007 Sun Microsystems, Inc. All Rights Reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Sun designates this +# particular file as subject to the "Classpath" exception as provided +# by Sun in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, +# CA 95054 USA or visit www.sun.com if you need additional information or +# have any questions. +# + + +#***************************************************************************** +#* Copyright (c) IBM Corporation 1998 * +#* * +#* (C) Copyright IBM Corp. 1998 * +#* * +#***************************************************************************** + +# To add a generator sun.rmi.rmic.Foo which is invoked via the -foo option: +# +# 1. Add "foo" to generator.args list. +# 2. Add line: generator.class.foo=sun.rmi.rmic.Foo +# 3. Update rmic.usage string to include new arguments. + +# For each available generator, list the command line argument used +# to invoke it. The value can be a single item or a comma separated +# list. + +generator.args=v1.1,vcompat,v1.2,iiop,idl,xprint + +# For each generator, specify the class to invoke, using the following +# syntax: +# +# generator.class.{arg}=fullClassName +# +# The 'default' entry is required and will be used if none of the args +# specified in generator.args is passed. Note that {arg} is compared +# using String.equalsIgnoreCase(). + +generator.class.default=sun.rmi.rmic.RMIGenerator + +generator.class.v1.1=sun.rmi.rmic.RMIGenerator +generator.class.vcompat=sun.rmi.rmic.RMIGenerator +generator.class.v1.2=sun.rmi.rmic.RMIGenerator +generator.class.iiop=sun.rmi.rmic.iiop.StubGenerator +generator.class.idl=sun.rmi.rmic.iiop.IDLGenerator +generator.class.xprint=sun.rmi.rmic.iiop.PrintGenerator + +# If a generator needs a BatchEnvironment other than +# sun.rmi.rmic.BatchEnvironment, specify it as follows: +# +# generator.env.{arg}=fullClassName + +generator.env.iiop=sun.rmi.rmic.iiop.BatchEnvironment +generator.env.idl=sun.rmi.rmic.iiop.BatchEnvironment +generator.env.xprint=sun.rmi.rmic.iiop.BatchEnvironment + +rmic.usage=\u4f7f\u3044\u65b9: {0} <options> <class names>\ +\n\ +\n<options> \u306b\u306f\u6b21\u306e\u3082\u306e\u304c\u3042\u308a\u307e\u3059\u3002\ +\n -keep \u4e2d\u9593\u751f\u6210\u3055\u308c\u305f\u30bd\u30fc\u30b9\u30d5\u30a1\u30a4\u30eb\u3092\u524a\u9664\u3057\u306a\u3044\ +\n -keepgenerated ("-keep" \u3068\u540c\u3058)\ +\n -v1.1 1.1 \u30b9\u30bf\u30d6\u30d7\u30ed\u30c8\u30b3\u30eb\u7248\u7528\u306e\u30b9\u30bf\u30d6/\u30b9\u30b1\u30eb\u30c8\u30f3\u3092\u4f5c\u6210\u3059\u308b\ +\n -vcompat 1.1 \u3068 1.2 \u306e\u30b9\u30bf\u30d6\u30d7\u30ed\u30c8\u30b3\u30eb\u7248\u3068\ +\n \u4e92\u63db\u6027\u306e\u3042\u308b\u30b9\u30bf\u30d6/\u30b9\u30b1\u30eb\u30c8\u30f3\u3092\u4f5c\u6210\u3059\u308b\ +\n -v1.2 (\u30c7\u30d5\u30a9\u30eb\u30c8) 1.2 \u30b9\u30bf\u30d6\u30d7\u30ed\u30c8\u30b3\u30eb\u7248\u5c02\u7528\u306e\u30b9\u30bf\u30d6\u3092\u4f5c\u6210\u3059\u308b\ +\n -iiop IIOP \u7528\u306e\u30b9\u30bf\u30d6\u3092\u4f5c\u6210\u3059\u308b\u3002\u6307\u5b9a\u3059\u308b\u3068 <options> \u306b\u306f\u6b21\u306e\u3082\u306e\u3082\u542b\u307e\u308c\u307e\u3059\u3002\ +\n\ +\n -always \u6700\u65b0\u306e\u5834\u5408\u3067\u3082\u30b9\u30bf\u30d6\u3092\u4f5c\u6210\u3059\u308b\ +\n -alwaysgenerate ("-always" \u3068\u540c\u3058)\ +\n -nolocalstubs \u540c\u3058\u30d7\u30ed\u30bb\u30b9\u306b\u3064\u3044\u3066\u6700\u9069\u5316\u3055\u308c\u305f\u30b9\u30bf\u30d6\u306f\u4f5c\u6210\u3057\u306a\u3044\ +\n\ +\n -idl IDL \u3092\u4f5c\u6210\u3059\u308b\u3002\u6307\u5b9a\u3059\u308b\u3068 <options> \u306b\u306f\u6b21\u306e\u3082\u306e\u3082\u542b\u307e\u308c\u307e\u3059\u3002\ +\n\ +\n -noValueMethods valuetypes \u306b\u5bfe\u3057\u3066\u30e1\u30bd\u30c3\u30c9\u3092\u751f\u6210\u3057\u306a\u3044 \ +\n -always \u6700\u65b0\u306e\u5834\u5408\u3067\u3082 IDL \u3092\u751f\u6210\u3059\u308b\ +\n -alwaysgenerate ("-always" \u3068\u540c\u3058)\ +\n\ +\n -g \u30c7\u30d0\u30c3\u30b0\u60c5\u5831\u3092\u751f\u6210\u3059\u308b\ +\n -nowarn \u8b66\u544a\u3092\u751f\u6210\u3057\u306a\u3044\ +\n -nowrite \u30b3\u30f3\u30d1\u30a4\u30eb\u3057\u305f\u30af\u30e9\u30b9\u3092\u30d5\u30a1\u30a4\u30eb\u30b7\u30b9\u30c6\u30e0\u306b\u66f8\u304d\u8fbc\u307e\u306a\u3044\ +\n -verbose \u30b3\u30f3\u30d1\u30a4\u30e9\u306e\u52d5\u4f5c\u306b\u95a2\u3059\u308b\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u51fa\u529b\u3059\u308b\ +\n -classpath <path> \u5165\u529b\u30af\u30e9\u30b9\u30d5\u30a1\u30a4\u30eb\u3092\u691c\u7d22\u3059\u308b\u4f4d\u7f6e\u3092\u6307\u5b9a\u3059\u308b\ +\n -bootclasspath <path> \u30d6\u30fc\u30c8\u30b9\u30c8\u30e9\u30c3\u30d7\u30af\u30e9\u30b9\u30d5\u30a1\u30a4\u30eb\u306e\u4f4d\u7f6e\u3092\u7f6e\u304d\u63db\u3048\u308b\ +\n -extdirs <path> \u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u6e08\u307f\u62e1\u5f35\u6a5f\u80fd\u306e\u4f4d\u7f6e\u3092\u7f6e\u304d\u63db\u3048\u308b\ +\n -d <directory> \u751f\u6210\u3055\u308c\u305f\u30af\u30e9\u30b9\u30d5\u30a1\u30a4\u30eb\u3092\u683c\u7d0d\u3059\u308b\u4f4d\u7f6e\u3092\u6307\u5b9a\u3059\u308b\ +\n -J<runtime flag> java \u30a4\u30f3\u30bf\u30d7\u30ea\u30bf\u306b\u5f15\u6570\u3092\u6e21\u3059 +\n\ + +# +# Generic Messages +# + +rmic.cant.read={0} \u3092\u8aad\u307f\u8fbc\u3080\u3053\u3068\u304c\u3067\u304d\u307e\u305b\u3093\u3002 +rmic.cant.write={0} \u304c\u66f8\u304d\u8fbc\u3081\u307e\u305b\u3093\u3002 +rmic.option.unsupported=\u30aa\u30d7\u30b7\u30e7\u30f3 {0} \u306f\u73fe\u5728\u306f\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002 +rmic.option.unimplemented=\u30aa\u30d7\u30b7\u30e7\u30f3 {0} \u306f\u307e\u3060\u5b9f\u88c5\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002 +rmic.option.already.seen=\u30aa\u30d7\u30b7\u30e7\u30f3 {0} \u306e\u6307\u5b9a\u306f 1 \u56de\u3060\u3051\u3067\u3059\u3002 +rmic.option.requires.argument=\u30aa\u30d7\u30b7\u30e7\u30f3 {0} \u306b\u306f\u5f15\u6570\u304c\u5fc5\u8981\u3067\u3059\u3002 +rmic.no.such.directory=\u30c7\u30a3\u30ec\u30af\u30c8\u30ea {0} \u306f\u5b58\u5728\u3057\u307e\u305b\u3093\u3002 +rmic.no.such.option={0} \u306f\u4e0d\u6b63\u306a\u30aa\u30d7\u30b7\u30e7\u30f3\u307e\u305f\u306f\u5f15\u6570\u3067\u3059\u3002 +rmic.wrote=[{0} \u3092\u66f8\u304d\u8fbc\u307f\u307e\u3057\u305f] +rmic.errors=\u30a8\u30e9\u30fc {0} \u500b +rmic.1error=\u30a8\u30e9\u30fc 1 \u500b +rmic.warnings=\u8b66\u544a {0} \u500b +rmic.1warning=\u8b66\u544a 1 \u500b +rmic.done_in=[{0} ms \u3067\u5b8c\u4e86] +rmic.no.memory=\ + \u30b3\u30f3\u30d1\u30a4\u30e9\u306b\u30e1\u30e2\u30ea\u30fc\u304c\u4e0d\u8db3\u3057\u3066\u3044\u307e\u3059\u3002"-J-Xmx<size>" \u30b3\u30de\u30f3\u30c9\u884c\u30aa\u30d7\u30b7\u30e7\u30f3\u3092\u4f7f\u3063\u3066\u3001\u6700\u5927\u30d2\u30fc\u30d7\u30b5\u30a4\u30ba\u3092\u5897\u3084\u3057\u3066\u304f\u3060\u3055\u3044\u3002 +rmic.stack.overflow=\ + \u30b3\u30f3\u30d1\u30a4\u30e9\u306b\u30b9\u30bf\u30c3\u30af\u7a7a\u9593\u304c\u4e0d\u8db3\u3057\u3066\u3044\u307e\u3059\u3002"-J-Xss<size>" \u30b3\u30de\u30f3\u30c9\u884c\u30aa\u30d7\u30b7\u30e7\u30f3\u3092\u4f7f\u3063\u3066\u3001Java \u30b9\u30bf\u30c3\u30af\u306b\u5272\u308a\u5f53\u3066\u308b\u30e1\u30e2\u30ea\u30fc\u3092\u5897\u3084\u3057\u3066\u304f\u3060\u3055\u3044\u3002 +rmic.class.not.found=\u30af\u30e9\u30b9 {0} \u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002 +rmic.missing.property=\u30d7\u30ed\u30d1\u30c6\u30a3 generator.class.{0} \u304c\u3042\u308a\u307e\u305b\u3093\u3002 +rmic.cannot.instantiate=\u30af\u30e9\u30b9 {0} \u306e\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u3092\u751f\u6210\u3067\u304d\u307e\u305b\u3093\u3002 +rmic.cannot.use.both={0} \u3068 {1} \u306e\u4e21\u65b9\u3092\u4f7f\u3046\u3053\u3068\u304c\u3067\u304d\u307e\u305b\u3093\u3002 +rmic.resource.not.found={0} \u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002 +rmic.no.output.dir=\ + {0} \u306b\u9069\u5207\u306a\u51fa\u529b\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002-d \u3092\u4f7f\u7528\u3057\u3066\u30eb\u30fc\u30c8\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u3092\u6307\u5b9a\u3057\u3066\u304f\u3060\u3055\u3044\u3002 +rmic.cannot.create.dir=\ + \u51fa\u529b\u30c7\u30a3\u30ec\u30af\u30c8\u30ea {0} \u3092\u4f5c\u6210\u3067\u304d\u307e\u305b\u3093\u3002 + +# +# JRMP Messages +# + +rmic.cant.make.stubs.for.interface=\ + {0} \u306f\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3067\u3059\u3002\u30b9\u30bf\u30d6\u306f\u30ea\u30e2\u30fc\u30c8\u30aa\u30d6\u30b8\u30a7\u30af\u30c8\u30af\u30e9\u30b9\u3060\u3051\u306b\u5fc5\u8981\u3067\u3059\u3002 +rmic.must.implement.remote=\ + \u30af\u30e9\u30b9 {0} \u306f java.rmi.Remote \u3092\u62e1\u5f35\u3059\u308b\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3092\u5b9f\u88c5\u3057\u307e\u305b\u3093\u3002\u30b9\u30bf\u30d6\u3068\u30b9\u30b1\u30eb\u30c8\u30f3\u3092\u5fc5\u8981\u3068\u3059\u308b\u306e\u306f\u30ea\u30e2\u30fc\u30c8\u30aa\u30d6\u30b8\u30a7\u30af\u30c8\u3060\u3051\u3067\u3059\u3002 +rmic.must.implement.remote.directly=\ + \u30b9\u30bf\u30d6\u3092\u5fc5\u8981\u3068\u3059\u308b\u306e\u306f java.rmi.Remote \u3092\u62e1\u5f35\u3059\u308b\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3092\u76f4\u63a5\u5b9f\u88c5\u3059\u308b\u30af\u30e9\u30b9\u3060\u3051\u3067\u3059\u3002\n\t\u30af\u30e9\u30b9 {0} \u306f\u30ea\u30e2\u30fc\u30c8\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3092\u76f4\u63a5\u306b\u306f\u5b9f\u88c5\u3057\u307e\u305b\u3093\u3002 +rmic.must.throw.remoteexception=\ + {0} \u306f\u4e0d\u6b63\u306a\u30ea\u30e2\u30fc\u30c8\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3067\u3059\u3002\u30e1\u30bd\u30c3\u30c9 {1} \u306f java.rmi.RemoteException \u3092\u30b9\u30ed\u30fc\u3057\u306a\u3051\u308c\u3070\u306a\u308a\u307e\u305b\u3093\u3002 +rmic.must.only.throw.exception=\ + \u30e1\u30bd\u30c3\u30c9 {0} \u306f {1} \u3092\u30b9\u30ed\u30fc\u3059\u308b\u306e\u3067\u4e0d\u6b63\u306a\u30ea\u30e2\u30fc\u30c8\u30e1\u30bd\u30c3\u30c9\u5b9f\u88c5\u3067\u3059\u3002\u30ea\u30e2\u30fc\u30c8\u30e1\u30bd\u30c3\u30c9\u306e\u5b9f\u88c5\u304c\u30b9\u30ed\u30fc\u3059\u308b\u306e\u306f java.lang.Exception \u304b\u305d\u306e\u30b5\u30d6\u30af\u30e9\u30b9\u3060\u3051\u3067\u3059\u3002 +warn.rmic.tie.found=\ + IIOP "tie" \u306f\u30af\u30e9\u30b9 {0} \u306e\u305f\u3081\u306b\u3042\u308a\u307e\u3059\u3002\ + \n {1}\ + \nPortableRemoteObject.exportObject \u3092\u4f7f\u3046\u5834\u5408\u306f\u3053\u306e\u30d5\u30a1\u30a4\u30eb\u3092\u524a\u9664\u3057\u307e\u3059\u3002\u524a\u9664\u3057\u306a\u3044\u3068\u3001\u30b5\u30fc\u30d0\u30fc\u30aa\u30d6\u30b8\u30a7\u30af\u30c8\u306f JRMP \u3067\u306a\u304f IIOP \u306b\u30a8\u30af\u30b9\u30dd\u30fc\u30c8\u3055\u308c\u307e\u3059\u3002 + +# +# RMI-IIOP Messages +# + +rmic.generated=[{1} ms \u3067 {0} \u3092\u751f\u6210] +rmic.previously.generated=[\u4ee5\u524d\u306b\u751f\u6210\u3055\u308c\u305f\u30d5\u30a1\u30a4\u30eb {0} \u306f\u6700\u65b0\u3067\u3059] +warn.rmic.member.not.mapped=\ + \u30af\u30e9\u30b9 {1} \u306e\u30c7\u30fc\u30bf\u30e1\u30f3\u30d0\u30fc {0} \u306f IDL \u306b\u30de\u30c3\u30d7\u3055\u308c\u307e\u305b\u3093\u3067\u3057\u305f\u3002 + +rmic.iiop.constraint.1=\ + {0} \u306f\u4e0d\u6b63\u306a\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3067\u3059\u3002java.rmi.Remote \u304b\u3089\u306f\u7d99\u627f\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002 +rmic.iiop.constraint.2=\ + \u30af\u30e9\u30b9 {0} \u306e serialPersistentFields \u914d\u5217\u306f\u4e0d\u6b63\u3067\u3059\u3002\u5b58\u5728\u3057\u306a\u3044\u30e1\u30f3\u30d0\u30fc\u3092\u53c2\u7167\u3057\u3066\u3044\u307e\u3059\u3002 +rmic.iiop.constraint.3=\ + {0} \u306f\u4e0d\u6b63\u306a\u30ea\u30e2\u30fc\u30c8\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3067\u3059\u3002{1} \u306f\u6b63\u3057\u3044\u30d7\u30ea\u30df\u30c6\u30a3\u30d6\u307e\u305f\u306f String \u5b9a\u6570\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002 +rmic.iiop.constraint.4=\ + {0} \u306f\u4e0d\u6b63\u306a\u5024\u3067\u3059\u3002serialPersistentFields \u306f private static final \u3067\u306a\u3051\u308c\u3070\u306a\u308a\u307e\u305b\u3093\u3002 +rmic.iiop.constraint.5=\ + {0} \u306f\u4e0d\u6b63\u306a\u30ea\u30e2\u30fc\u30c8\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3067\u3059\u3002\u30e1\u30bd\u30c3\u30c9 {1} \u306f RemoteException \u307e\u305f\u306f RemoteException \u306e\u30b9\u30fc\u30d1\u30fc\u30af\u30e9\u30b9\u3092\u30b9\u30ed\u30fc\u3059\u3079\u304d\u3067\u3059\u3002 +rmic.iiop.constraint.6=\ + {0} \u306f\u4e0d\u6b63\u306a\u30ea\u30e2\u30fc\u30c8\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3067\u3059\u3002\u7d99\u627f\u3055\u308c\u305f\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9 {1} \u3082\u4e21\u65b9\u30e1\u30bd\u30c3\u30c9 {2} \u3092\u5ba3\u8a00\u3057\u3066\u3044\u307e\u3059\u3002 +rmic.iiop.constraint.7=\ + {0} \u306f\u4e0d\u6b63\u306a\u578b\u3067\u3059\u3002{1} \u306f\u7279\u5225\u306a\u5834\u5408\u306b\u306e\u307f\u7570\u306a\u308a\u307e\u3059\u3002 +rmic.iiop.constraint.8=\ + {0} \u306f\u4e0d\u6b63\u306a\u30ea\u30e2\u30fc\u30c8\u5b9f\u88c5\u3067\u3059\u3002\u30ea\u30e2\u30fc\u30c8\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3092\u6301\u3063\u3066\u3044\u307e\u305b\u3093\u3002 +rmic.iiop.constraint.9=\ + \u30af\u30e9\u30b9 {1} \u306e serialPersistentFields \u914d\u5217\u30e1\u30f3\u30d0\u30fc {0} \u306f\u4e0d\u6b63\u3067\u3059\u3002\u578b\u304c\u5ba3\u8a00\u3055\u308c\u305f\u30e1\u30f3\u30d0\u30fc\u3068\u4e00\u81f4\u3057\u307e\u305b\u3093\u3002 +rmic.iiop.constraint.10=\ + {0} \u306f\u4e0d\u6b63\u306a\u5024\u3067\u3059\u3002java.rmi.Remote \u3092\u5b9f\u88c5\u3057\u307e\u3059\u3002 +rmic.iiop.constraint.11=\ + {0} \u306f\u4e0d\u6b63\u306a\u5024\u3067\u3059\u3002java.io.Serializable \u3092\u5b9f\u88c5\u3057\u3066\u3044\u307e\u305b\u3093\u3002 +rmic.iiop.constraint.12=\ + {0} \u306f\u4e0d\u6b63\u306a\u5024\u3067\u3059\u3002\u4e0d\u6b63\u306a\u89aa\u3067\u3059\u3002 +rmic.iiop.constraint.13=\ + {0} \u306f\u4e0d\u6b63\u306a\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3067\u3059\u3002\u30e1\u30bd\u30c3\u30c9 {1} \u306e idl \u540d\u304c\u4ed6\u306e\u30e1\u30bd\u30c3\u30c9\u3068\u7af6\u5408\u3057\u307e\u3059\u3002 +rmic.iiop.constraint.14=\ + {0} \u306f\u4e0d\u6b63\u306a abstract \u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3067\u3059\u3002\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002 +rmic.iiop.constraint.15=\ + {0} \u306f\u4e0d\u6b63\u306a abstract \u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3067\u3059\u3002java.rmi.Remote \u3092\u5b9f\u88c5\u3057\u3066\u3044\u307e\u3059\u3002 +rmic.iiop.constraint.16=\ + {0} \u306f\u4e0d\u6b63\u306a\u30ea\u30e2\u30fc\u30c8\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3067\u3059\u3001\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002 +rmic.iiop.constraint.17=\ + {0} \u306f\u4e0d\u6b63\u306a\u30ea\u30e2\u30fc\u30c8\u5b9f\u88c5\u3067\u3059\u3002\u30af\u30e9\u30b9\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002 +rmic.iiop.constraint.18=\ + {0} \u306f\u4e0d\u6b63\u306a\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3067\u3059\u3002\u30e1\u30bd\u30c3\u30c9 {1} \u306f org.omg.CORBA.portable.IDLEntity \u3092\u5b9f\u88c5\u3059\u308b\u4f8b\u5916\u3092\u6e21\u3057\u307e\u305b\u3093\u3002 +rmic.iiop.constraint.19=\ + {0} \u306f\u4e0d\u6b63\u306a\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3067\u3059\u3002\u5b9a\u6570 {1} \u306e idl \u540d\u304c\u4ed6\u306e\u5b9a\u6570\u3068\u7af6\u5408\u3057\u307e\u3059\u3002 +rmic.iiop.constraint.20=\ + {0} \u306f\u4e0d\u6b63\u306a\u30af\u30e9\u30b9\u3067\u3059\u3002\u30e1\u30f3\u30d0\u30fc {1} \u306e idl \u540d\u304c\u4ed6\u306e\u30e1\u30f3\u30d0\u30fc\u3068\u7af6\u5408\u3057\u307e\u3059\u3002 +rmic.iiop.constraint.21=\ + {0} \u306f\u30ea\u30e2\u30fc\u30c8\u5b9f\u88c5\u30af\u30e9\u30b9\u3067\u3042\u308a\u3001{1} \u306e\u30e1\u30bd\u30c3\u30c9\u5f15\u6570\u3042\u308b\u3044\u306f\u623b\u308a\u578b\u3068\u3057\u3066\u306f\u4f7f\u7528\u3067\u304d\u307e\u305b\u3093\u3002 +rmic.iiop.constraint.22=\ + \u5185\u90e8\u969c\u5bb3: (\u30e1\u30bd\u30c3\u30c9) \u4f8b\u5916 {0} \u306f\u30af\u30e9\u30b9\u578b\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002 +rmic.iiop.constraint.23=\ + \u5185\u90e8\u969c\u5bb3: (\u30e1\u30bd\u30c3\u30c9) \u306f {0} \u306e null \u30dd\u30a4\u30f3\u30bf\u4f8b\u5916\u3092\u30ad\u30e3\u30c3\u30c1\u3057\u307e\u3057\u305f\u3002 +rmic.iiop.constraint.24=\ + \u30af\u30e9\u30b9 {0} \u306f\u4e0d\u6b63\u306a\u623b\u308a\u578b\u3092\u542b\u3093\u3067\u3044\u307e\u3059\u3002 +rmic.iiop.constraint.25=\ + \u30af\u30e9\u30b9 {0} \u306f\u30e1\u30bd\u30c3\u30c9 {1} \u306b\u4e0d\u6b63\u306a\u5f15\u6570\u578b\u3092\u542b\u3093\u3067\u3044\u307e\u3059\u3002 +rmic.iiop.constraint.26=\ + {0} \u3092\u30b3\u30f3\u30d1\u30a4\u30eb\u3067\u304d\u307e\u305b\u3093\u3002 +rmic.iiop.constraint.27=\ + \u30af\u30e9\u30b9 {0} \u3092\u30ed\u30fc\u30c9\u3067\u304d\u307e\u305b\u3093\u3002 +rmic.iiop.constraint.28=\ + {0} \u306f\u30ea\u30e2\u30fc\u30c8\u5b9f\u88c5\u30af\u30e9\u30b9\u3067\u3042\u308a\u3001{1} \u306e\u30c7\u30fc\u30bf\u30e1\u30f3\u30d0\u30fc\u3068\u3057\u3066\u306f\u4f7f\u7528\u3067\u304d\u307e\u305b\u3093\u3002 diff --git a/src/share/classes/sun/rmi/rmic/resources/rmic_zh_CN.properties b/src/share/classes/sun/rmi/rmic/resources/rmic_zh_CN.properties new file mode 100644 index 000000000..2fd51a021 --- /dev/null +++ b/src/share/classes/sun/rmi/rmic/resources/rmic_zh_CN.properties @@ -0,0 +1,222 @@ +# +# +# Copyright 2005-2007 Sun Microsystems, Inc. All Rights Reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Sun designates this +# particular file as subject to the "Classpath" exception as provided +# by Sun in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, +# CA 95054 USA or visit www.sun.com if you need additional information or +# have any questions. +# + + +#***************************************************************************** +#* Copyright (c) IBM Corporation 1998 * +#* * +#* (C) Copyright IBM Corp. 1998 * +#* * +#***************************************************************************** + +# To add a generator sun.rmi.rmic.Foo which is invoked via the -foo option: +# +# 1. Add "foo" to generator.args list. +# 2. Add line: generator.class.foo=sun.rmi.rmic.Foo +# 3. Update rmic.usage string to include new arguments. + +# For each available generator, list the command line argument used +# to invoke it. The value can be a single item or a comma separated +# list. + +generator.args=v1.1,vcompat,v1.2,iiop,idl,xprint + +# For each generator, specify the class to invoke, using the following +# syntax: +# +# generator.class.{arg}=fullClassName +# +# The 'default' entry is required and will be used if none of the args +# specified in generator.args is passed. Note that {arg} is compared +# using String.equalsIgnoreCase(). + +generator.class.default=sun.rmi.rmic.RMIGenerator + +generator.class.v1.1=sun.rmi.rmic.RMIGenerator +generator.class.vcompat=sun.rmi.rmic.RMIGenerator +generator.class.v1.2=sun.rmi.rmic.RMIGenerator +generator.class.iiop=sun.rmi.rmic.iiop.StubGenerator +generator.class.idl=sun.rmi.rmic.iiop.IDLGenerator +generator.class.xprint=sun.rmi.rmic.iiop.PrintGenerator + +# If a generator needs a BatchEnvironment other than +# sun.rmi.rmic.BatchEnvironment, specify it as follows: +# +# generator.env.{arg}=fullClassName + +generator.env.iiop=sun.rmi.rmic.iiop.BatchEnvironment +generator.env.idl=sun.rmi.rmic.iiop.BatchEnvironment +generator.env.xprint=sun.rmi.rmic.iiop.BatchEnvironment + +rmic.usage=\u7528\u6cd5\uff1a{0} <\u9009\u9879> <\u7c7b\u540d\u79f0>\ +\n\ +\n\u5176\u4e2d <\u9009\u9879> \u5305\u62ec\uff1a\ +\n -keep \u4e0d\u5220\u9664\u4e2d\u95f4\u751f\u6210\u7684\u6e90\u6587\u4ef6\ +\n -keepgenerated\uff08\u4e0e "-keep" \u76f8\u540c\uff09\ +\n -v1.1 \u521b\u5efa 1.1 \u5b58\u6839\u534f\u8bae\u7248\u672c\u7684\u5b58\u6839/\u6846\u67b6\ +\n -vcompat \u521b\u5efa\u4e0e 1.1 \u548c 1.2 \u5b58\u6839\u534f\u8bae\u7248\u672c\u90fd\u517c\u5bb9\u7684\ +\n \u5b58\u6839/\u6846\u67b6\ +\n -v1.2 \uff08\u9ed8\u8ba4\u503c\uff09\u4ec5\u521b\u5efa 1.2 \u5b58\u6839\u534f\u8bae\u7248\u672c\u7684\u5b58\u6839\ +\n -iiop \u521b\u5efa IIOP \u7684\u5b58\u6839\u3002\u4f7f\u7528\u6b64\u9009\u9879\u65f6\uff0c<\u9009\u9879> \u8fd8\u5305\u62ec\uff1a\ +\n\ +\n -always \u5373\u4f7f\u5f53\u524d\u663e\u793a\u5b58\u6839\uff0c\u4ecd\u521b\u5efa\u5b83\u4eec\ +\n -alwaysgenerate \uff08\u4e0e "-always" \u76f8\u540c\uff09\ +\n -nolocalstubs \u4e0d\u521b\u5efa\u4e3a\u76f8\u540c\u8fdb\u7a0b\u4f18\u5316\u7684\u5b58\u6839\ +\n\ +\n -idl \u521b\u5efa IDL\u3002\u4f7f\u7528\u6b64\u9009\u9879\u65f6\uff0c<\u9009\u9879> \u8fd8\u5305\u62ec\uff1a\ +\n\ +\n -noValueMethods \u4e0d\u751f\u6210 valuetypes \u7684\u65b9\u6cd5\ +\n -always \u5373\u4f7f\u5f53\u524d\u663e\u793a IDL\uff0c\u4ecd\u521b\u5efa\u5b83\ +\n -alwaysgenerate \uff08\u4e0e "-always" \u76f8\u540c\uff09\ +\n\ +\n -g \u751f\u6210\u8c03\u8bd5\u4fe1\u606f\ +\n -nowarn \u4e0d\u751f\u6210\u4efb\u4f55\u8b66\u544a\ +\n -nowrite \u4e0d\u5411\u6587\u4ef6\u7cfb\u7edf\u5199\u5165\u7f16\u8bd1\u7684\u7c7b\ +\n -verbose \u8f93\u51fa\u6709\u5173\u7f16\u8bd1\u5668\u6b63\u5728\u6267\u884c\u7684\u64cd\u4f5c\u7684\u6d88\u606f\ +\n -classpath <\u8def\u5f84> \u6307\u5b9a\u67e5\u627e\u8f93\u5165\u7c7b\u6587\u4ef6\u7684\u4f4d\u7f6e\ +\n -bootclasspath <\u8def\u5f84> \u8986\u76d6\u5f15\u5bfc\u7c7b\u6587\u4ef6\u7684\u4f4d\u7f6e\ +\n -extdirs <\u8def\u5f84> \u8986\u76d6\u5b89\u88c5\u7684\u6269\u5c55\u76ee\u5f55\u7684\u4f4d\u7f6e\ +\n -d <\u76ee\u5f55> \u6307\u5b9a\u5b58\u653e\u751f\u6210\u7684\u7c7b\u6587\u4ef6\u7684\u4f4d\u7f6e\ +\n -J <\u8fd0\u884c\u65f6\u6807\u5fd7> \u5411 java \u89e3\u91ca\u7a0b\u5e8f\u4f20\u9012\u53c2\u6570 +\n\ + +# +# Generic Messages +# + +rmic.cant.read=\u65e0\u6cd5\u8bfb\u53d6\uff1a{0} +rmic.cant.write=\u65e0\u6cd5\u5199\u5165\uff1a{0} +rmic.option.unsupported=\u4e0d\u518d\u652f\u6301 {0} \u9009\u9879\u3002 +rmic.option.unimplemented=\u4ecd\u672a\u5b9e\u73b0 {0} \u9009\u9879\u3002 +rmic.option.already.seen={0} \u9009\u9879\u53ea\u80fd\u88ab\u6307\u5b9a\u4e00\u6b21\u3002 +rmic.option.requires.argument=\u9009\u9879 {0} \u9700\u8981\u53c2\u6570\u3002 +rmic.no.such.directory={0} \u76ee\u5f55\u4e0d\u5b58\u5728\u3002 +rmic.no.such.option={0} \u4e3a\u65e0\u6548\u9009\u9879\u6216\u53c2\u6570\u3002 +rmic.wrote=[\u5df2\u5199\u5165 {0}] +rmic.errors={0} \u4e2a\u9519\u8bef +rmic.1error=1 \u4e2a\u9519\u8bef +rmic.warnings={0} \u4e2a\u8b66\u544a +rmic.1warning=1 \u4e2a\u8b66\u544a +rmic.done_in=[\u5728 {0} \u6beb\u79d2\u5185\u5b8c\u6210] +rmic.no.memory=\ + \u7f16\u8bd1\u5668\u6240\u7528\u5185\u5b58\u4e0d\u8db3\u3002\u8bf7\u8003\u8651\u4f7f\u7528\u201c-J-Xmx<\u5927\u5c0f>\u201d\u547d\u4ee4\u884c\u9009\u9879\u6765\u589e\u52a0\u5806\u5927\u5c0f\u7684\u6700\u5927\u503c\u3002 +rmic.stack.overflow=\ + \u7f16\u8bd1\u5668\u6240\u7528\u5806\u6808\u7a7a\u95f4\u4e0d\u8db3\u3002\u8bf7\u8003\u8651\u4f7f\u7528\u201c-J-Xss<\u5927\u5c0f>\u201d\u547d\u4ee4\u884c\u9009\u9879\u6765\u589e\u52a0\u5206\u914d\u7ed9 Java \u5806\u6808\u7684\u5185\u5b58\u5927\u5c0f\u3002 +rmic.class.not.found=\ + \u672a\u627e\u5230\u7c7b {0}\u3002 +rmic.missing.property=\u7f3a\u5c11\u5c5e\u6027 generator.class.{0} +rmic.cannot.instantiate=\u65e0\u6cd5\u5b9e\u4f8b\u5316\u7c7b {0} +rmic.cannot.use.both=\u65e0\u6cd5\u540c\u65f6\u4f7f\u7528 {0} \u548c {1} +rmic.resource.not.found=\u672a\u627e\u5230 {0}\u3002 +rmic.no.output.dir=\ + \u65e0\u6cd5\u627e\u5230\u9002\u7528\u4e8e {0} \u7684\u8f93\u51fa\u76ee\u5f55\u3002\u8bf7\u4f7f\u7528 -d \u9009\u9879\u6765\u6307\u5b9a\u6839\u76ee\u5f55\u3002 +rmic.cannot.create.dir=\ + \u65e0\u6cd5\u521b\u5efa\u8f93\u51fa\u76ee\u5f55 {0}\u3002 + +# +# JRMP Messages +# + +rmic.cant.make.stubs.for.interface=\ + {0} \u662f\u63a5\u53e3\uff1b\u4ec5\u8fdc\u7a0b\u5bf9\u8c61\u7c7b\u9700\u8981\u5b58\u6839\u3002 +rmic.must.implement.remote=\ + \u7c7b {0} \u4e0d\u5b9e\u73b0\u6269\u5c55 java.rmi.Remote \u7684\u63a5\u53e3\uff1b\u4ec5\u8fdc\u7a0b\u5bf9\u8c61\u9700\u8981\u5b58\u6839\u548c\u6846\u67b6\u3002 +rmic.must.implement.remote.directly=\ + \u4ec5\u76f4\u63a5\u5b9e\u73b0\u6269\u5c55 java.rmi.Remote \u7684\u63a5\u53e3\u7684\u7c7b\u9700\u8981\u5b58\u6839\uff1b\u7c7b {0} \u4e0d\u76f4\u63a5\u5b9e\u73b0\u8fdc\u7a0b\u63a5\u53e3\u3002 +rmic.must.throw.remoteexception=\ + {0} \u4e0d\u662f\u6709\u6548\u8fdc\u7a0b\u63a5\u53e3\uff1a\u65b9\u6cd5 {1} \u5fc5\u987b\u629b\u51fa java.rmi.RemoteException\u3002 +rmic.must.only.throw.exception=\ + \u65b9\u6cd5 {0} \u4e0d\u662f\u6709\u6548\u7684\u8fdc\u7a0b\u65b9\u6cd5\u5b9e\u73b0\uff0c\u56e0\u4e3a\u5b83\u629b\u51fa\u4e86 {1}\uff1b\u8fdc\u7a0b\u65b9\u6cd5\u5b9e\u73b0\u53ea\u53ef\u80fd\u629b\u51fa java.lang.Exception \u6216\u5176\u5b50\u7c7b\u3002 +warn.rmic.tie.found=\ + \u5bf9\u4e8e\u7c7b {0}\uff0c\u5b58\u5728 IIOP "tie"\uff1a\ + \n {1}\ + \n\u5982\u679c\u4f7f\u7528 PortableRemoteObject.exportObject\uff0c\u5219\u5e94\u8be5\u5220\u9664\u6b64\u6587\u4ef6\uff0c\u5426\u5219\uff0c\u60a8\u7684\u670d\u52a1\u5668\u5bf9\u8c61\u5c06\u4f1a\u88ab\u5bfc\u51fa\u5230 IIOP \u800c\u975e JRMP\u3002 + +# +# RMI-IIOP Messages +# + +rmic.generated=[\u5728 {1} \u6beb\u79d2\u5185\u751f\u6210\u7684 {0}] +rmic.previously.generated=[\u4ee5\u524d\u751f\u6210\u7684\u6587\u4ef6 {0} \u4e3a\u5f53\u524d\u6587\u4ef6] +warn.rmic.member.not.mapped=\ +\u7c7b {1} \u7684\u6570\u636e\u6210\u5458 {0} \u672a\u6620\u5c04\u5230 IDL\u3002 + +rmic.iiop.constraint.1=\ +{0} \u4e0d\u662f\u6709\u6548\u63a5\u53e3\uff1a\u6ca1\u6709\u4ece java.rmi.Remote \u7ee7\u627f\u3002 +rmic.iiop.constraint.2=\ +\u7c7b {0} \u7684 serialPersistentFields \u6570\u7ec4\u65e0\u6548\uff1a\u5f15\u7528\u4e86\u4e0d\u5b58\u5728\u7684\u6210\u5458\u3002 +rmic.iiop.constraint.3=\ +{0} \u4e0d\u662f\u6709\u6548\u8fdc\u7a0b\u63a5\u53e3\uff1a{1} \u4e0d\u662f\u6709\u6548\u7684\u539f\u59cb\u6216\u5b57\u7b26\u4e32\u5e38\u91cf\u3002 +rmic.iiop.constraint.4=\ +{0} \u4e0d\u662f\u6709\u6548\u503c\uff1aserialPersistentFields \u5fc5\u987b\u4e3a\u4e13\u7528\u9759\u6001\u6700\u7ec8\u7c7b\u578b\u3002 +rmic.iiop.constraint.5=\ +{0} \u4e0d\u662f\u6709\u6548\u8fdc\u7a0b\u63a5\u53e3\uff1a\u65b9\u6cd5 {1} \u5fc5\u987b\u629b\u51fa RemoteException \u6216 RemoteException \u7684\u7236\u7c7b\u3002 +rmic.iiop.constraint.6=\ +{0} \u4e0d\u662f\u6709\u6548\u8fdc\u7a0b\u63a5\u53e3\uff1a\u7ee7\u627f\u7684\u63a5\u53e3 {1} \u5747\u58f0\u660e\u4e86\u65b9\u6cd5 {2}\u3002 +rmic.iiop.constraint.7=\ +{0} \u4e0d\u662f\u6709\u6548\u7c7b\u578b\uff1a{1} \u4ec5\u5728\u5927\u5c0f\u5199\u4e0a\u4e0d\u540c\u3002 +rmic.iiop.constraint.8=\ +{0} \u4e0d\u662f\u6709\u6548\u8fdc\u7a0b\u5b9e\u73b0\uff1a\u4e0d\u5177\u6709\u8fdc\u7a0b\u63a5\u53e3\u3002 +rmic.iiop.constraint.9=\ +\u7c7b {1} \u7684 serialPersistentFields \u6570\u7ec4\u6210\u5458 {0} \u65e0\u6548\uff1a\u7c7b\u578b\u4e0e\u58f0\u660e\u7684\u6210\u5458\u4e0d\u5339\u914d\u3002 +rmic.iiop.constraint.10=\ +{0} \u4e0d\u662f\u6709\u6548\u503c\uff1a\u5b9e\u73b0 java.rmi.Remote\u3002 +rmic.iiop.constraint.11=\ +{0} \u4e0d\u662f\u6709\u6548\u503c\uff1a\u4e0d\u5b9e\u73b0 java.io.Serializable\u3002 +rmic.iiop.constraint.12=\ +{0} \u4e0d\u662f\u6709\u6548\u503c\uff1a\u7236\u503c\u65e0\u6548\u3002 +rmic.iiop.constraint.13=\ +{0} \u4e0d\u662f\u6709\u6548\u63a5\u53e3\uff1a\u65b9\u6cd5 {1} \u7684 idl \u540d\u79f0\u4e0e\u5176\u4ed6\u65b9\u6cd5\u51b2\u7a81\u3002 +rmic.iiop.constraint.14=\ +{0} \u4e0d\u662f\u6709\u6548\u62bd\u8c61\u63a5\u53e3\uff1a\u4e0d\u662f\u63a5\u53e3\u3002 +rmic.iiop.constraint.15=\ +{0} \u4e0d\u662f\u6709\u6548\u62bd\u8c61\u63a5\u53e3\uff1a\u5b9e\u73b0 java.rmi.Remote\u3002 +rmic.iiop.constraint.16=\ +{0} \u4e0d\u662f\u6709\u6548\u8fdc\u7a0b\u63a5\u53e3\uff1a\u4e0d\u662f\u63a5\u53e3\u3002 +rmic.iiop.constraint.17=\ +{0} \u4e0d\u662f\u6709\u6548\u8fdc\u7a0b\u5b9e\u73b0\uff1a\u4e0d\u662f\u7c7b\u3002 +rmic.iiop.constraint.18=\ +{0} \u4e0d\u662f\u6709\u6548\u63a5\u53e3\uff1a\u65b9\u6cd5 {1} \u4e0d\u80fd\u4f20\u9012\u5b9e\u73b0 org.omg.CORBA.portable.IDLEntity \u7684\u5f02\u5e38\u3002 +rmic.iiop.constraint.19=\ +{0} \u4e0d\u662f\u6709\u6548\u63a5\u53e3\uff1a\u5e38\u91cf {1} \u7684 idl \u540d\u79f0\u4e0e\u5176\u4ed6\u5e38\u91cf\u51b2\u7a81\u3002 +rmic.iiop.constraint.20=\ +{0} \u4e0d\u662f\u6709\u6548\u7c7b\uff1a\u6210\u5458 {1} \u7684 idl \u540d\u79f0\u4e0e\u5176\u4ed6\u6210\u5458\u51b2\u7a81\u3002 +rmic.iiop.constraint.21=\ +{0} \u662f\u8fdc\u7a0b\u5b9e\u73b0\u7c7b\u5e76\u4e14\u4e0d\u80fd\u7528\u4f5c {1} \u4e2d\u7684\u65b9\u6cd5\u53c2\u6570\u6216\u8fd4\u56de\u7c7b\u578b\u3002 +rmic.iiop.constraint.22=\ +\u5185\u90e8\u5931\u8d25\uff1a\uff08\u65b9\u6cd5\uff09\u5f02\u5e38 {0} \u4e0d\u662f\u7c7b\u7c7b\u578b\u3002 +rmic.iiop.constraint.23=\ +\u5185\u90e8\u5931\u8d25\uff1a\uff08\u65b9\u6cd5\uff09\u6355\u6349\u5230 {0} \u7684\u7a7a\u6307\u9488\u5f02\u5e38\u3002 +rmic.iiop.constraint.24=\ +\u7c7b {0} \u5305\u542b\u65e0\u6548\u8fd4\u56de\u7c7b\u578b\u3002 +rmic.iiop.constraint.25=\ +\u7c7b {0} \u5305\u542b\u65b9\u6cd5 {1} \u4e2d\u7684\u65e0\u6548\u53c2\u6570\u7c7b\u578b\u3002 +rmic.iiop.constraint.26=\ +\u65e0\u6cd5\u7f16\u8bd1 {0}\u3002 +rmic.iiop.constraint.27=\ +\u65e0\u6cd5\u88c5\u5165\u7c7b {0}\u3002 +rmic.iiop.constraint.28=\ +{0} \u662f\u8fdc\u7a0b\u5b9e\u73b0\u7c7b\u5e76\u4e14\u65e0\u6cd5\u7528\u4f5c {1} \u4e2d\u7684\u6570\u636e\u6210\u5458\u3002 diff --git a/src/share/classes/sun/rmi/runtime/Log.java b/src/share/classes/sun/rmi/runtime/Log.java new file mode 100644 index 000000000..3eb8ce9cf --- /dev/null +++ b/src/share/classes/sun/rmi/runtime/Log.java @@ -0,0 +1,456 @@ +/* + * Copyright 2001-2002 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.runtime; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.io.OutputStream; +import java.rmi.server.LogStream; +import java.util.logging.ConsoleHandler; +import java.util.logging.Handler; +import java.util.logging.Formatter; +import java.util.logging.SimpleFormatter; +import java.util.logging.StreamHandler; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.logging.LogManager; +import java.util.logging.LogRecord; +import java.util.logging.StreamHandler; +import java.util.Map; +import java.util.HashMap; + +/** + * Utility which provides an abstract "logger" like RMI internal API + * which can be directed to use one of two types of logging + * infrastructure: the java.util.logging API or the + * java.rmi.server.LogStream API. The default behavior is to use the + * java.util.logging API. The LogStream API may be used instead by + * setting the system property sun.rmi.log.useOld to true. + * + * For backwards compatibility, supports the RMI system logging + * properties which pre-1.4 comprised the only way to configure RMI + * logging. If the java.util.logging API is used and RMI system log + * properties are set, the system properties override initial RMI + * logger values as appropriate. If the java.util.logging API is + * turned off, pre-1.4 logging behavior is used. + * + * @author Laird Dornin + * @since 1.4 + */ +public abstract class Log { + + /** Logger re-definition of old RMI log values */ + public static final Level BRIEF = Level.FINE; + public static final Level VERBOSE = Level.FINER; + + /* selects log implementation */ + private static final LogFactory logFactory; + static { + boolean useOld = + Boolean.valueOf((String) java.security.AccessController. + doPrivileged(new sun.security.action.GetPropertyAction( + "sun.rmi.log.useOld"))).booleanValue(); + + /* set factory to select the logging facility to use */ + logFactory = (useOld ? (LogFactory) new LogStreamLogFactory() : + (LogFactory) new LoggerLogFactory()); + } + + /** "logger like" API to be used by RMI implementation */ + public abstract boolean isLoggable(Level level); + public abstract void log(Level level, String message); + public abstract void log(Level level, String message, Throwable thrown); + + /** get and set the RMI server call output stream */ + public abstract void setOutputStream(OutputStream stream); + public abstract PrintStream getPrintStream(); + + /** factory interface enables Logger and LogStream implementations */ + private static interface LogFactory { + Log createLog(String loggerName, String oldLogName, Level level); + } + + /* access log objects */ + + /** + * Access log for a tri-state system property. + * + * Need to first convert override value to a log level, taking + * care to interpret a range of values between BRIEF, VERBOSE and + * SILENT. + * + * An override < 0 is interpreted to mean that the logging + * configuration should not be overridden. The level passed to the + * factories createLog method will be null in this case. + * + * Note that if oldLogName is null and old logging is on, the + * returned LogStreamLog will ignore the override parameter - the + * log will never log messages. This permits new logs that only + * write to Loggers to do nothing when old logging is active. + * + * Do not call getLog multiple times on the same logger name. + * Since this is an internal API, no checks are made to ensure + * that multiple logs do not exist for the same logger. + */ + public static Log getLog(String loggerName, String oldLogName, + int override) + { + Level level; + + if (override < 0) { + level = null; + } else if (override == LogStream.SILENT) { + level = Level.OFF; + } else if ((override > LogStream.SILENT) && + (override <= LogStream.BRIEF)) { + level = BRIEF; + } else if ((override > LogStream.BRIEF) && + (override <= LogStream.VERBOSE)) + { + level = VERBOSE; + } else { + level = Level.FINEST; + } + return logFactory.createLog(loggerName, oldLogName, level); + } + + /** + * Access logs associated with boolean properties + * + * Do not call getLog multiple times on the same logger name. + * Since this is an internal API, no checks are made to ensure + * that multiple logs do not exist for the same logger. + */ + public static Log getLog(String loggerName, String oldLogName, + boolean override) + { + Level level = (override ? VERBOSE : null); + return logFactory.createLog(loggerName, oldLogName, level); + } + + /** + * Factory to create Log objects which deliver log messages to the + * java.util.logging API. + */ + private static class LoggerLogFactory implements LogFactory { + LoggerLogFactory() {} + + /* + * Accessor to obtain an arbitrary RMI logger with name + * loggerName. If the level of the logger is greater than the + * level for the system property with name, the logger level + * will be set to the value of system property. + */ + public Log createLog(final String loggerName, String oldLogName, + final Level level) + { + Logger logger = Logger.getLogger(loggerName); + return new LoggerLog(logger, level); + } + } + + /** + * Class specialized to log messages to the java.util.logging API + */ + private static class LoggerLog extends Log { + + /* alternate console handler for RMI loggers */ + private static final Handler alternateConsole = (Handler) + java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + InternalStreamHandler alternate = + new InternalStreamHandler(System.err); + alternate.setLevel(Level.ALL); + return alternate; + } + } + ); + + /** handler to which messages are copied */ + private InternalStreamHandler copyHandler = null; + + /* logger to which log messages are written */ + private final Logger logger; + + /* used as return value of RemoteServer.getLog */ + private LoggerPrintStream loggerSandwich; + + /** creates a Log which will delegate to the given logger */ + private LoggerLog(final Logger logger, final Level level) { + this.logger = logger; + + if (level != null){ + java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + if (!logger.isLoggable(level)) { + logger.setLevel(level); + } + logger.addHandler(alternateConsole); + return null; + } + } + ); + } + } + + public boolean isLoggable(Level level) { + return logger.isLoggable(level); + } + + public void log(Level level, String message) { + if (isLoggable(level)) { + String[] source = getSource(); + logger.logp(level, source[0], source[1], + Thread.currentThread().getName() + ": " + message); + } + } + + public void log(Level level, String message, Throwable thrown) { + if (isLoggable(level)) { + String[] source = getSource(); + logger.logp(level, source[0], source[1], + Thread.currentThread().getName() + ": " + + message, thrown); + } + } + + /** + * Set the output stream associated with the RMI server call + * logger. + * + * Calling code needs LoggingPermission "control". + */ + public synchronized void setOutputStream(OutputStream out) { + if (out != null) { + if (!logger.isLoggable(VERBOSE)) { + logger.setLevel(VERBOSE); + } + copyHandler = new InternalStreamHandler(out); + copyHandler.setLevel(Log.VERBOSE); + logger.addHandler(copyHandler); + } else { + /* ensure that messages are not logged */ + if (copyHandler != null) { + logger.removeHandler(copyHandler); + } + copyHandler = null; + } + } + + public synchronized PrintStream getPrintStream() { + if (loggerSandwich == null) { + loggerSandwich = new LoggerPrintStream(logger); + } + return loggerSandwich; + } + } + + /** + * Subclass of StreamHandler for redirecting log output. flush + * must be called in the publish and close methods. + */ + private static class InternalStreamHandler extends StreamHandler { + InternalStreamHandler(OutputStream out) { + super(out, new SimpleFormatter()); + } + + public void publish(LogRecord record) { + super.publish(record); + flush(); + } + + public void close() { + flush(); + } + } + + /** + * PrintStream which forwards log messages to the logger. Class + * is needed to maintain backwards compatibility with + * RemoteServer.{set|get}Log(). + */ + private static class LoggerPrintStream extends PrintStream { + + /** logger where output of this log is sent */ + private final Logger logger; + + /** record the last character written to this stream */ + private int last = -1; + + /** stream used for buffering lines */ + private final ByteArrayOutputStream bufOut; + + private LoggerPrintStream(Logger logger) + { + super(new ByteArrayOutputStream()); + bufOut = (ByteArrayOutputStream) super.out; + this.logger = logger; + } + + public void write(int b) { + if ((last == '\r') && (b == '\n')) { + last = -1; + return; + } else if ((b == '\n') || (b == '\r')) { + try { + /* write the converted bytes of the log message */ + String message = + Thread.currentThread().getName() + ": " + + bufOut.toString(); + logger.logp(Level.INFO, "LogStream", "print", message); + } finally { + bufOut.reset(); + } + } else { + super.write(b); + } + last = b; + } + + public void write(byte b[], int off, int len) { + if (len < 0) { + throw new ArrayIndexOutOfBoundsException(len); + } + for (int i = 0; i < len; i++) { + write(b[off + i]); + } + } + + public String toString() { + return "RMI"; + } + } + + /** + * Factory to create Log objects which deliver log messages to the + * java.rmi.server.LogStream API + */ + private static class LogStreamLogFactory implements LogFactory { + LogStreamLogFactory() {} + + /* create a new LogStreamLog for the specified log */ + public Log createLog(String loggerName, String oldLogName, + Level level) + { + LogStream stream = null; + if (oldLogName != null) { + stream = LogStream.log(oldLogName); + } + return new LogStreamLog(stream, level); + } + } + + /** + * Class specialized to log messages to the + * java.rmi.server.LogStream API + */ + private static class LogStreamLog extends Log { + /** Log stream to which log messages are written */ + private final LogStream stream; + + /** the level of the log as set by associated property */ + private int levelValue = Level.OFF.intValue(); + + private LogStreamLog(LogStream stream, Level level) { + if ((stream != null) && (level != null)) { + /* if the stream or level is null, dont log any + * messages + */ + levelValue = level.intValue(); + } + this.stream = stream; + } + + public synchronized boolean isLoggable(Level level) { + return (level.intValue() >= levelValue); + } + + public void log(Level messageLevel, String message) { + if (isLoggable(messageLevel)) { + String[] source = getSource(); + stream.println(unqualifiedName(source[0]) + + "." + source[1] + ": " + message); + } + } + + public void log(Level level, String message, Throwable thrown) { + if (isLoggable(level)) { + /* + * keep output contiguous and maintain the contract of + * RemoteServer.getLog + */ + synchronized (stream) { + String[] source = getSource(); + stream.println(unqualifiedName(source[0]) + "." + + source[1] + ": " + message); + thrown.printStackTrace(stream); + } + } + } + + public PrintStream getPrintStream() { + return stream; + } + + public synchronized void setOutputStream(OutputStream out) { + if (out != null) { + if (VERBOSE.intValue() < levelValue) { + levelValue = VERBOSE.intValue(); + } + stream.setOutputStream(out); + } else { + /* ensure that messages are not logged */ + levelValue = Level.OFF.intValue(); + } + } + + /* + * Mimic old log messages that only contain unqualified names. + */ + private static String unqualifiedName(String name) { + int lastDot = name.lastIndexOf("."); + if (lastDot >= 0) { + name = name.substring(lastDot + 1); + } + name = name.replace('$', '.'); + return name; + } + } + + /** + * Obtain class and method names of code calling a log method. + */ + private static String[] getSource() { + StackTraceElement[] trace = (new Exception()).getStackTrace(); + return new String[] { + trace[3].getClassName(), + trace[3].getMethodName() + }; + } +} diff --git a/src/share/classes/sun/rmi/runtime/NewThreadAction.java b/src/share/classes/sun/rmi/runtime/NewThreadAction.java new file mode 100644 index 000000000..65c411d4f --- /dev/null +++ b/src/share/classes/sun/rmi/runtime/NewThreadAction.java @@ -0,0 +1,138 @@ +/* + * Copyright 2000-2005 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.runtime; + +import java.security.AccessController; +import java.security.PrivilegedAction; +import sun.security.util.SecurityConstants; + +/** + * A PrivilegedAction for creating a new thread conveniently with an + * AccessController.doPrivileged construct. + * + * All constructors allow the choice of the Runnable for the new + * thread to execute, the name of the new thread (which will be + * prefixed with "RMI "), and whether or not it will be a daemon + * thread. + * + * The new thread may be created in the system thread group (the root + * of the thread group tree) or an internally created non-system + * thread group, as specified at construction of this class. + * + * The new thread will have the system class loader as its initial + * context class loader (that is, its context class loader will NOT be + * inherited from the current thread). + * + * @author Peter Jones + **/ +public final class NewThreadAction implements PrivilegedAction<Thread> { + + /** cached reference to the system (root) thread group */ + static final ThreadGroup systemThreadGroup = + AccessController.doPrivileged(new PrivilegedAction<ThreadGroup>() { + public ThreadGroup run() { + ThreadGroup group = Thread.currentThread().getThreadGroup(); + ThreadGroup parent; + while ((parent = group.getParent()) != null) { + group = parent; + } + return group; + } + }); + + /** + * special child of the system thread group for running tasks that + * may execute user code, so that the security policy for threads in + * the system thread group will not apply + */ + static final ThreadGroup userThreadGroup = + AccessController.doPrivileged(new PrivilegedAction<ThreadGroup>() { + public ThreadGroup run() { + return new ThreadGroup(systemThreadGroup, "RMI Runtime"); + } + }); + + private final ThreadGroup group; + private final Runnable runnable; + private final String name; + private final boolean daemon; + + NewThreadAction(ThreadGroup group, Runnable runnable, + String name, boolean daemon) + { + this.group = group; + this.runnable = runnable; + this.name = name; + this.daemon = daemon; + } + + /** + * Creates an action that will create a new thread in the + * system thread group. + * + * @param runnable the Runnable for the new thread to execute + * + * @param name the name of the new thread + * + * @param daemon if true, new thread will be a daemon thread; + * if false, new thread will not be a daemon thread + */ + public NewThreadAction(Runnable runnable, String name, boolean daemon) { + this(systemThreadGroup, runnable, name, daemon); + } + + /** + * Creates an action that will create a new thread. + * + * @param runnable the Runnable for the new thread to execute + * + * @param name the name of the new thread + * + * @param daemon if true, new thread will be a daemon thread; + * if false, new thread will not be a daemon thread + * + * @param user if true, thread will be created in a non-system + * thread group; if false, thread will be created in the system + * thread group + */ + public NewThreadAction(Runnable runnable, String name, boolean daemon, + boolean user) + { + this(user ? userThreadGroup : systemThreadGroup, + runnable, name, daemon); + } + + public Thread run() { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION); + } + Thread t = new Thread(group, runnable, "RMI " + name); + t.setContextClassLoader(ClassLoader.getSystemClassLoader()); + t.setDaemon(daemon); + return t; + } +} diff --git a/src/share/classes/sun/rmi/runtime/RuntimeUtil.java b/src/share/classes/sun/rmi/runtime/RuntimeUtil.java new file mode 100644 index 000000000..3146db77f --- /dev/null +++ b/src/share/classes/sun/rmi/runtime/RuntimeUtil.java @@ -0,0 +1,131 @@ +/* + * Copyright 2005 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.runtime; + +import java.security.AccessController; +import java.security.Permission; +import java.security.PrivilegedAction; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import sun.security.action.GetIntegerAction; + +/** + * RMI runtime implementation utilities. + * + * There is a single instance of this class, which can be obtained + * with a GetInstanceAction. Getting the instance requires + * RuntimePermission("sun.rmi.runtime.RuntimeUtil.getInstance") + * because the public methods of this class expose security-sensitive + * capabilities. + * + * @author Peter Jones + **/ +public final class RuntimeUtil { + + /** runtime package log */ + private static final Log runtimeLog = + Log.getLog("sun.rmi.runtime", null, false); + + /** number of scheduler threads */ + private static final int schedulerThreads = // default 1 + AccessController.doPrivileged( + new GetIntegerAction("sun.rmi.runtime.schedulerThreads", 1)); + + /** permission required to get instance */ + private static final Permission GET_INSTANCE_PERMISSION = + new RuntimePermission("sun.rmi.runtime.RuntimeUtil.getInstance"); + + /** the singleton instance of this class */ + private static final RuntimeUtil instance = new RuntimeUtil(); + + /** thread pool for scheduling delayed tasks */ + private final ScheduledThreadPoolExecutor scheduler; + + private RuntimeUtil() { + scheduler = new ScheduledThreadPoolExecutor( + schedulerThreads, + new ThreadFactory() { + private final AtomicInteger count = new AtomicInteger(0); + public Thread newThread(Runnable runnable) { + try { + return AccessController.doPrivileged( + new NewThreadAction(runnable, + "Scheduler(" + count.getAndIncrement() + ")", + true)); + } catch (Throwable t) { + runtimeLog.log(Level.WARNING, + "scheduler thread factory throws", t); + return null; + } + } + }); + /* + * We would like to allow the scheduler's threads to terminate + * if possible, but a bug in DelayQueue.poll can cause code + * like this to result in a busy loop: + */ + // stpe.setKeepAliveTime(10, TimeUnit.MINUTES); + // stpe.allowCoreThreadTimeOut(true); + } + + /** + * A PrivilegedAction for getting the RuntimeUtil instance. + **/ + public static class GetInstanceAction + implements PrivilegedAction<RuntimeUtil> + { + /** + * Creates an action that returns the RuntimeUtil instance. + **/ + public GetInstanceAction() { + } + + public RuntimeUtil run() { + return getInstance(); + } + } + + private static RuntimeUtil getInstance() { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(GET_INSTANCE_PERMISSION); + } + return instance; + } + + /** + * Returns the shared thread pool for scheduling delayed tasks. + * + * Note that the returned pool has limited concurrency, so + * submitted tasks should be short-lived and should not block. + **/ + public ScheduledThreadPoolExecutor getScheduler() { + return scheduler; + } +} diff --git a/src/share/classes/sun/rmi/server/ActivatableRef.java b/src/share/classes/sun/rmi/server/ActivatableRef.java new file mode 100644 index 000000000..94af9b01b --- /dev/null +++ b/src/share/classes/sun/rmi/server/ActivatableRef.java @@ -0,0 +1,411 @@ +/* + * Copyright 1997-2003 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.server; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.lang.reflect.Proxy; +import java.net.MalformedURLException; +import java.net.URL; +import java.rmi.*; +import java.rmi.activation.*; +import java.rmi.server.Operation; +import java.rmi.server.RMIClassLoader; +import java.rmi.server.RemoteCall; +import java.rmi.server.RemoteObject; +import java.rmi.server.RemoteObjectInvocationHandler; +import java.rmi.server.RemoteRef; +import java.rmi.server.RemoteStub; + +public class ActivatableRef implements RemoteRef { + + private static final long serialVersionUID = 7579060052569229166L; + + protected ActivationID id; + protected RemoteRef ref; + transient boolean force = false; + + private static final int MAX_RETRIES = 3; + private static final String versionComplaint = + "activation requires 1.2 stubs"; + + /** + * Create a new (empty) ActivatableRef + */ + public ActivatableRef() + {} + + /** + * Create a ActivatableRef with the specified id + */ + public ActivatableRef(ActivationID id, RemoteRef ref) + { + this.id = id; + this.ref = ref; + } + + /** + * Returns the stub for the remote object whose class is + * specified in the activation descriptor. The ActivatableRef + * in the resulting stub has its activation id set to the + * activation id supplied as the second argument. + */ + public static Remote getStub(ActivationDesc desc, ActivationID id) + throws StubNotFoundException + { + String className = desc.getClassName(); + + try { + Class cl = + RMIClassLoader.loadClass(desc.getLocation(), className); + RemoteRef clientRef = new ActivatableRef(id, null); + return Util.createProxy(cl, clientRef, false); + + } catch (IllegalArgumentException e) { + throw new StubNotFoundException( + "class implements an illegal remote interface", e); + + } catch (ClassNotFoundException e) { + throw new StubNotFoundException("unable to load class: " + + className, e); + } catch (MalformedURLException e) { + throw new StubNotFoundException("malformed URL", e); + } + } + + /** + * Invoke method on remote object. This method delegates remote + * method invocation to the underlying ref type. If the + * underlying reference is not known (is null), then the object + * must be activated first. If an attempt at method invocation + * fails, the object should force reactivation. Method invocation + * must preserve "at most once" call semantics. In RMI, "at most + * once" applies to parameter deserialization at the remote site + * and the remote object's method execution. "At most once" does + * not apply to parameter serialization at the client so the + * parameters of a call don't need to be buffered in anticipation + * of call retry. Thus, a method call is only be retried if the + * initial method invocation does not execute at all at the server + * (including parameter deserialization). + */ + public Object invoke(Remote obj, + java.lang.reflect.Method method, + Object[] params, + long opnum) + throws Exception + { + + boolean force = false; + RemoteRef localRef; + Exception exception = null; + + /* + * Attempt object activation if active ref is unknown. + * Throws a RemoteException if object can't be activated. + */ + synchronized (this) { + if (ref == null) { + localRef = activate(force); + force = true; + } else { + localRef = ref; + } + } + + for (int retries = MAX_RETRIES; retries > 0; retries--) { + + try { + return localRef.invoke(obj, method, params, opnum); + } catch (NoSuchObjectException e) { + /* + * Object is not active in VM; retry call + */ + exception = e; + } catch (ConnectException e) { + /* + * Failure during connection setup; retry call + */ + exception = e; + } catch (UnknownHostException e) { + /* + * Failure during connection setup; retry call. + */ + exception = e; + } catch (ConnectIOException e) { + /* + * Failure setting up multiplexed connection or reusing + * cached connection; retry call + */ + exception = e; + } catch (MarshalException e) { + /* + * Failure during parameter serialization; call may + * have reached server, so call retry not possible. + */ + throw e; + } catch (ServerError e) { + /* + * Call reached server; propagate remote exception. + */ + throw e; + } catch (ServerException e) { + /* + * Call reached server; propagate remote exception + */ + throw e; + } catch (RemoteException e) { + /* + * This is a catch-all for other RemoteExceptions. + * UnmarshalException being the only one relevant. + * + * StubNotFoundException should never show up because + * it is generally thrown when attempting to locate + * a stub. + * + * UnexpectedException should never show up because + * it is only thrown by a stub and would be wrapped + * in a ServerException if it was propagated by a + * remote call. + */ + synchronized (this) { + if (localRef == ref) { + ref = null; // this may be overly conservative + } + } + + throw e; + } + + if (retries > 1) { + /* + * Activate object, since object could not be reached. + */ + synchronized (this) { + if (localRef.remoteEquals(ref) || ref == null) { + RemoteRef newRef = activate(force); + + if (newRef.remoteEquals(localRef) && + exception instanceof NoSuchObjectException && + force == false) { + /* + * If last exception was NoSuchObjectException, + * then old value of ref is definitely wrong, + * so make sure that it is different. + */ + newRef = activate(true); + } + + localRef = newRef; + force = true; + } else { + localRef = ref; + force = false; + } + } + } + } + + /* + * Retries unsuccessful, so throw last exception + */ + throw exception; + } + + /** + * private method to obtain the ref for a call. + */ + private synchronized RemoteRef getRef() + throws RemoteException + { + if (ref == null) { + ref = activate(false); + } + + return ref; + } + + /** + * private method to activate the remote object. + * + * NOTE: the caller must be synchronized on "this" before + * calling this method. + */ + private RemoteRef activate(boolean force) + throws RemoteException + { + assert Thread.holdsLock(this); + + ref = null; + try { + /* + * Activate the object and retrieve the remote reference + * from inside the stub returned as the result. Then + * set this activatable ref's internal ref to be the + * ref inside the ref of the stub. In more clear terms, + * the stub returned from the activate call contains an + * ActivatableRef. We need to set the ref in *this* + * ActivatableRef to the ref inside the ActivatableRef + * retrieved from the stub. The ref type embedded in the + * ActivatableRef is typically a UnicastRef. + */ + + Remote proxy = id.activate(force); + ActivatableRef newRef = null; + + if (proxy instanceof RemoteStub) { + newRef = (ActivatableRef) ((RemoteStub) proxy).getRef(); + } else { + /* + * Assume that proxy is an instance of a dynamic proxy + * class. If that assumption is not correct, or either of + * the casts below fails, the resulting exception will be + * wrapped in an ActivateFailedException below. + */ + RemoteObjectInvocationHandler handler = + (RemoteObjectInvocationHandler) + Proxy.getInvocationHandler(proxy); + newRef = (ActivatableRef) handler.getRef(); + } + ref = newRef.ref; + return ref; + + } catch (ConnectException e) { + throw new ConnectException("activation failed", e); + } catch (RemoteException e) { + throw new ConnectIOException("activation failed", e); + } catch (UnknownObjectException e) { + throw new NoSuchObjectException("object not registered"); + } catch (ActivationException e) { + throw new ActivateFailedException("activation failed", e); + } + } + + /** + * This call is used by the old 1.1 stub protocol and is + * unsupported since activation requires 1.2 stubs. + */ + public synchronized RemoteCall newCall(RemoteObject obj, + Operation[] ops, + int opnum, + long hash) + throws RemoteException + { + throw new UnsupportedOperationException(versionComplaint); + } + + /** + * This call is used by the old 1.1 stub protocol and is + * unsupported since activation requires 1.2 stubs. + */ + public void invoke(RemoteCall call) throws Exception + { + throw new UnsupportedOperationException(versionComplaint); + } + + /** + * This call is used by the old 1.1 stub protocol and is + * unsupported since activation requires 1.2 stubs. + */ + public void done(RemoteCall call) throws RemoteException { + throw new UnsupportedOperationException(versionComplaint); + } + + /** + * Returns the class of the ref type to be serialized + */ + public String getRefClass(ObjectOutput out) + { + return "ActivatableRef"; + } + + /** + * Write out external representation for remote ref. + */ + public void writeExternal(ObjectOutput out) throws IOException + { + RemoteRef localRef = ref; + + out.writeObject(id); + if (localRef == null) { + out.writeUTF(""); + } else { + out.writeUTF(localRef.getRefClass(out)); + localRef.writeExternal(out); + } + } + + /** + * Read in external representation for remote ref. + * @exception ClassNotFoundException If the class for an object + * being restored cannot be found. + */ + public void readExternal(ObjectInput in) + throws IOException, ClassNotFoundException + { + id = (ActivationID)in.readObject(); + ref = null; + String className = in.readUTF(); + + if (className.equals("")) return; + + try { + Class refClass = Class.forName(RemoteRef.packagePrefix + "." + + className); + ref = (RemoteRef)refClass.newInstance(); + ref.readExternal(in); + } catch (InstantiationException e) { + throw new UnmarshalException("Unable to create remote reference", + e); + } catch (IllegalAccessException e) { + throw new UnmarshalException("Illegal access creating remote reference"); + } + } + + //----------------------------------------------------------------------; + /** + * Method from object, forward from RemoteObject + */ + public String remoteToString() { + return Util.getUnqualifiedName(getClass()) + + " [remoteRef: " + ref + "]"; + } + + /** + * default implementation of hashCode for remote objects + */ + public int remoteHashCode() { + return id.hashCode(); + } + + /** default implementation of equals for remote objects + */ + public boolean remoteEquals(RemoteRef ref) { + if (ref instanceof ActivatableRef) + return id.equals(((ActivatableRef)ref).id); + return false; + } +} diff --git a/src/share/classes/sun/rmi/server/ActivatableServerRef.java b/src/share/classes/sun/rmi/server/ActivatableServerRef.java new file mode 100644 index 000000000..6e439f0c4 --- /dev/null +++ b/src/share/classes/sun/rmi/server/ActivatableServerRef.java @@ -0,0 +1,93 @@ +/* + * Copyright 1997-2003 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.server; + +import java.io.IOException; +import java.io.NotSerializableException; +import java.io.ObjectOutput; +import java.rmi.*; +import java.rmi.server.*; +import java.rmi.activation.ActivationID; +import sun.rmi.transport.LiveRef; + +/** + * Server-side ref for a persistent remote impl. + * + * @author Ann Wollrath + */ +public class ActivatableServerRef extends UnicastServerRef2 { + + private static final long serialVersionUID = 2002967993223003793L; + + private ActivationID id; + + /** + * Construct a Unicast server remote reference to be exported + * on the specified port. + */ + public ActivatableServerRef(ActivationID id, int port) + { + this(id, port, null, null); + } + + /** + * Construct a Unicast server remote reference to be exported + * on the specified port. + */ + public ActivatableServerRef(ActivationID id, int port, + RMIClientSocketFactory csf, + RMIServerSocketFactory ssf) + { + super(new LiveRef(port, csf, ssf)); + this.id = id; + } + + /** + * Returns the class of the ref type to be serialized + */ + public String getRefClass(ObjectOutput out) + { + return "ActivatableServerRef"; + } + + /** + * Return the client remote reference for this remoteRef. + * In the case of a client RemoteRef "this" is the answer. + * For a server remote reference, a client side one will have to + * found or created. + */ + protected RemoteRef getClientRef() { + return new ActivatableRef(id, new UnicastRef2(ref)); + } + + /** + * Prevents serialization (because deserializaion is impossible). + */ + public void writeExternal(ObjectOutput out) throws IOException { + throw new NotSerializableException( + "ActivatableServerRef not serializable"); + } +} diff --git a/src/share/classes/sun/rmi/server/Activation.java b/src/share/classes/sun/rmi/server/Activation.java new file mode 100644 index 000000000..41c202737 --- /dev/null +++ b/src/share/classes/sun/rmi/server/Activation.java @@ -0,0 +1,2481 @@ +/* + * Copyright 1997-2006 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.server; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.Serializable; +import java.lang.Process; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketException; +import java.nio.channels.Channel; +import java.nio.channels.ServerSocketChannel; +import java.rmi.AccessException; +import java.rmi.AlreadyBoundException; +import java.rmi.ConnectException; +import java.rmi.ConnectIOException; +import java.rmi.MarshalledObject; +import java.rmi.NoSuchObjectException; +import java.rmi.NotBoundException; +import java.rmi.Remote; +import java.rmi.RemoteException; +import java.rmi.activation.ActivationDesc; +import java.rmi.activation.ActivationException; +import java.rmi.activation.ActivationGroupDesc; +import java.rmi.activation.ActivationGroup; +import java.rmi.activation.ActivationGroupID; +import java.rmi.activation.ActivationID; +import java.rmi.activation.ActivationInstantiator; +import java.rmi.activation.ActivationMonitor; +import java.rmi.activation.ActivationSystem; +import java.rmi.activation.Activator; +import java.rmi.activation.UnknownGroupException; +import java.rmi.activation.UnknownObjectException; +import java.rmi.registry.Registry; +import java.rmi.server.ObjID; +import java.rmi.server.RMIClassLoader; +import java.rmi.server.RMIClientSocketFactory; +import java.rmi.server.RMIServerSocketFactory; +import java.rmi.server.RemoteObject; +import java.rmi.server.RemoteServer; +import java.rmi.server.UnicastRemoteObject; +import java.security.AccessControlException; +import java.security.AccessController; +import java.security.AllPermission; +import java.security.CodeSource; +import java.security.Permission; +import java.security.PermissionCollection; +import java.security.Permissions; +import java.security.Policy; +import java.security.PrivilegedAction; +import java.security.PrivilegedExceptionAction; +import java.security.cert.Certificate; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.Properties; +import java.util.ResourceBundle; +import java.util.Set; +import sun.rmi.log.LogHandler; +import sun.rmi.log.ReliableLog; +import sun.rmi.registry.RegistryImpl; +import sun.rmi.runtime.NewThreadAction; +import sun.rmi.server.UnicastServerRef; +import sun.rmi.transport.LiveRef; +import sun.security.action.GetBooleanAction; +import sun.security.action.GetIntegerAction; +import sun.security.action.GetPropertyAction; +import sun.security.provider.PolicyFile; +import com.sun.rmi.rmid.ExecPermission; +import com.sun.rmi.rmid.ExecOptionPermission; + +/** + * The Activator facilitates remote object activation. A "faulting" + * remote reference calls the activator's <code>activate</code> method + * to obtain a "live" reference to a activatable remote object. Upon + * receiving a request for activation, the activator looks up the + * activation descriptor for the activation identifier, id, determines + * the group in which the object should be activated and invokes the + * activate method on the object's activation group (described by the + * remote interface <code>ActivationInstantiator</code>). The + * activator initiates the execution of activation groups as + * necessary. For example, if an activation group for a specific group + * identifier is not already executing, the activator will spawn a + * child process for the activation group. <p> + * + * The activator is responsible for monitoring and detecting when + * activation groups fail so that it can remove stale remote references + * from its internal tables. <p> + * + * @author Ann Wollrath + * @since 1.2 + */ +public class Activation implements Serializable { + + /** indicate compatibility with JDK 1.2 version of class */ + private static final long serialVersionUID = 2921265612698155191L; + + private static final byte MAJOR_VERSION = 1; + private static final byte MINOR_VERSION = 0; + + /** exec policy object */ + private static Object execPolicy; + private static Method execPolicyMethod; + private static boolean debugExec; + + /** maps activation id to its respective group id */ + private Map<ActivationID,ActivationGroupID> idTable = + new HashMap<ActivationID,ActivationGroupID>(); + /** maps group id to its GroupEntry groups */ + private Map<ActivationGroupID,GroupEntry> groupTable = + new HashMap<ActivationGroupID,GroupEntry>(); + + private byte majorVersion = MAJOR_VERSION; + private byte minorVersion = MINOR_VERSION; + + /** number of simultaneous group exec's */ + private transient int groupSemaphore; + /** counter for numbering groups */ + private transient int groupCounter; + /** reliable log to hold descriptor table */ + private transient ReliableLog log; + /** number of updates since last snapshot */ + private transient int numUpdates; + + /** the java command */ + // accessed by GroupEntry + private transient String[] command; + /** timeout on wait for child process to be created or destroyed */ + private static final long groupTimeout = + getInt("sun.rmi.activation.groupTimeout", 60000); + /** take snapshot after this many updates */ + private static final int snapshotInterval = + getInt("sun.rmi.activation.snapshotInterval", 200); + /** timeout on wait for child process to be created */ + private static final long execTimeout = + getInt("sun.rmi.activation.execTimeout", 30000); + + private static final Object initLock = new Object(); + private static boolean initDone = false; + + // this should be a *private* method since it is privileged + private static int getInt(String name, int def) { + return AccessController.doPrivileged(new GetIntegerAction(name, def)); + } + + private transient Activator activator; + private transient Activator activatorStub; + private transient ActivationSystem system; + private transient ActivationSystem systemStub; + private transient ActivationMonitor monitor; + private transient Registry registry; + private transient volatile boolean shuttingDown = false; + private transient volatile Object startupLock; + private transient Thread shutdownHook; + + private static ResourceBundle resources = null; + + /** + * Create an uninitialized instance of Activation that can be + * populated with log data. This is only called when the initial + * snapshot is taken during the first incarnation of rmid. + */ + private Activation() {} + + /** + * Recover activation state from the reliable log and initialize + * activation services. + */ + private static void startActivation(int port, + RMIServerSocketFactory ssf, + String logName, + String[] childArgs) + throws Exception + { + ReliableLog log = new ReliableLog(logName, new ActLogHandler()); + Activation state = (Activation) log.recover(); + state.init(port, ssf, log, childArgs); + } + + /** + * Initialize the Activation instantiation; start activation + * services. + */ + private void init(int port, + RMIServerSocketFactory ssf, + ReliableLog log, + String[] childArgs) + throws Exception + { + // initialize + this.log = log; + numUpdates = 0; + shutdownHook = new ShutdownHook(); + groupSemaphore = getInt("sun.rmi.activation.groupThrottle", 3); + groupCounter = 0; + Runtime.getRuntime().addShutdownHook(shutdownHook); + ActivationGroupID[] gids = + groupTable.keySet().toArray( + new ActivationGroupID[groupTable.size()]); + + synchronized (startupLock = new Object()) { + // all the remote methods briefly synchronize on startupLock + // (via checkShutdown) to make sure they don't happen in the + // middle of this block. This block must not cause any such + // incoming remote calls to happen, or deadlock would result! + activator = new ActivatorImpl(port, ssf); + activatorStub = (Activator) RemoteObject.toStub(activator); + system = new ActivationSystemImpl(port, ssf); + systemStub = (ActivationSystem) RemoteObject.toStub(system); + monitor = new ActivationMonitorImpl(port, ssf); + initCommand(childArgs); + registry = new SystemRegistryImpl(port, null, ssf, systemStub); + + if (ssf != null) { + synchronized (initLock) { + initDone = true; + initLock.notifyAll(); + } + } + } + startupLock = null; + + // restart services + for (int i = gids.length; --i >= 0; ) { + try { + getGroupEntry(gids[i]).restartServices(); + } catch (UnknownGroupException e) { + System.err.println( + getTextResource("rmid.restart.group.warning")); + e.printStackTrace(); + } + } + } + + private static class SystemRegistryImpl extends RegistryImpl { + + private static final String NAME = ActivationSystem.class.getName(); + private final ActivationSystem systemStub; + + SystemRegistryImpl(int port, + RMIClientSocketFactory csf, + RMIServerSocketFactory ssf, + ActivationSystem systemStub) + throws RemoteException + { + super(port, csf, ssf); + this.systemStub = systemStub; + } + + /** + * Returns the activation system stub if the specified name + * matches the activation system's class name, otherwise + * returns the result of invoking super.lookup with the specified + * name. + */ + public Remote lookup(String name) + throws RemoteException, NotBoundException + { + if (name.equals(NAME)) { + return systemStub; + } else { + return super.lookup(name); + } + } + + public String[] list() throws RemoteException { + String[] list1 = super.list(); + int length = list1.length; + String[] list2 = new String[length + 1]; + if (length > 0) { + System.arraycopy(list1, 0, list2, 0, length); + } + list2[length] = NAME; + return list2; + } + + public void bind(String name, Remote obj) + throws RemoteException, AlreadyBoundException, AccessException + { + if (name.equals(NAME)) { + throw new AccessException( + "binding ActivationSystem is disallowed"); + } else { + super.bind(name, obj); + } + } + + public void unbind(String name) + throws RemoteException, NotBoundException, AccessException + { + if (name.equals(NAME)) { + throw new AccessException( + "unbinding ActivationSystem is disallowed"); + } else { + super.unbind(name); + } + } + + + public void rebind(String name, Remote obj) + throws RemoteException, AccessException + { + if (name.equals(NAME)) { + throw new AccessException( + "binding ActivationSystem is disallowed"); + } else { + super.rebind(name, obj); + } + } + } + + + class ActivatorImpl extends RemoteServer implements Activator { + // Because ActivatorImpl has a fixed ObjID, it can be + // called by clients holding stale remote references. Each of + // its remote methods, then, must check startupLock (calling + // checkShutdown() is easiest). + + private static final long serialVersionUID = -3654244726254566136L; + + /** + * Construct a new Activator on a specified port. + */ + ActivatorImpl(int port, RMIServerSocketFactory ssf) + throws RemoteException + { + /* Server ref must be created and assigned before remote object + * 'this' can be exported. + */ + LiveRef lref = + new LiveRef(new ObjID(ObjID.ACTIVATOR_ID), port, null, ssf); + UnicastServerRef uref = new UnicastServerRef(lref); + ref = uref; + uref.exportObject(this, null, false); + } + + public MarshalledObject<? extends Remote> activate(ActivationID id, + boolean force) + throws ActivationException, UnknownObjectException, RemoteException + { + checkShutdown(); + return getGroupEntry(id).activate(id, force); + } + } + + class ActivationMonitorImpl extends UnicastRemoteObject + implements ActivationMonitor + { + private static final long serialVersionUID = -6214940464757948867L; + + ActivationMonitorImpl(int port, RMIServerSocketFactory ssf) + throws RemoteException + { + super(port, null, ssf); + } + + public void inactiveObject(ActivationID id) + throws UnknownObjectException, RemoteException + { + try { + checkShutdown(); + } catch (ActivationException e) { + return; + } + RegistryImpl.checkAccess("Activator.inactiveObject"); + getGroupEntry(id).inactiveObject(id); + } + + public void activeObject(ActivationID id, + MarshalledObject<? extends Remote> mobj) + throws UnknownObjectException, RemoteException + { + try { + checkShutdown(); + } catch (ActivationException e) { + return; + } + RegistryImpl.checkAccess("ActivationSystem.activeObject"); + getGroupEntry(id).activeObject(id, mobj); + } + + public void inactiveGroup(ActivationGroupID id, + long incarnation) + throws UnknownGroupException, RemoteException + { + try { + checkShutdown(); + } catch (ActivationException e) { + return; + } + RegistryImpl.checkAccess("ActivationMonitor.inactiveGroup"); + getGroupEntry(id).inactiveGroup(incarnation, false); + } + } + + + class ActivationSystemImpl + extends RemoteServer + implements ActivationSystem + { + private static final long serialVersionUID = 9100152600327688967L; + + // Because ActivationSystemImpl has a fixed ObjID, it can be + // called by clients holding stale remote references. Each of + // its remote methods, then, must check startupLock (calling + // checkShutdown() is easiest). + ActivationSystemImpl(int port, RMIServerSocketFactory ssf) + throws RemoteException + { + /* Server ref must be created and assigned before remote object + * 'this' can be exported. + */ + LiveRef lref = new LiveRef(new ObjID(4), port, null, ssf); + UnicastServerRef uref = new UnicastServerRef(lref); + ref = uref; + uref.exportObject(this, null); + } + + public ActivationID registerObject(ActivationDesc desc) + throws ActivationException, UnknownGroupException, RemoteException + { + checkShutdown(); + RegistryImpl.checkAccess("ActivationSystem.registerObject"); + + ActivationGroupID groupID = desc.getGroupID(); + ActivationID id = new ActivationID(activatorStub); + getGroupEntry(groupID).registerObject(id, desc, true); + return id; + } + + public void unregisterObject(ActivationID id) + throws ActivationException, UnknownObjectException, RemoteException + { + checkShutdown(); + RegistryImpl.checkAccess("ActivationSystem.unregisterObject"); + getGroupEntry(id).unregisterObject(id, true); + } + + public ActivationGroupID registerGroup(ActivationGroupDesc desc) + throws ActivationException, RemoteException + { + checkShutdown(); + RegistryImpl.checkAccess("ActivationSystem.registerGroup"); + checkArgs(desc, null); + + ActivationGroupID id = new ActivationGroupID(systemStub); + GroupEntry entry = new GroupEntry(id, desc); + // table insertion must take place before log update + synchronized (groupTable) { + groupTable.put(id, entry); + } + addLogRecord(new LogRegisterGroup(id, desc)); + return id; + } + + public ActivationMonitor activeGroup(ActivationGroupID id, + ActivationInstantiator group, + long incarnation) + throws ActivationException, UnknownGroupException, RemoteException + { + checkShutdown(); + RegistryImpl.checkAccess("ActivationSystem.activeGroup"); + + getGroupEntry(id).activeGroup(group, incarnation); + return monitor; + } + + public void unregisterGroup(ActivationGroupID id) + throws ActivationException, UnknownGroupException, RemoteException + { + checkShutdown(); + RegistryImpl.checkAccess("ActivationSystem.unregisterGroup"); + + // remove entry before unregister so state is updated before + // logged + synchronized (groupTable) { + GroupEntry entry = getGroupEntry(id); + groupTable.remove(id); + entry.unregisterGroup(true); + } + } + + public ActivationDesc setActivationDesc(ActivationID id, + ActivationDesc desc) + throws ActivationException, UnknownObjectException, RemoteException + { + checkShutdown(); + RegistryImpl.checkAccess("ActivationSystem.setActivationDesc"); + + if (!getGroupID(id).equals(desc.getGroupID())) { + throw new ActivationException( + "ActivationDesc contains wrong group"); + } + return getGroupEntry(id).setActivationDesc(id, desc, true); + } + + public ActivationGroupDesc setActivationGroupDesc(ActivationGroupID id, + ActivationGroupDesc desc) + throws ActivationException, UnknownGroupException, RemoteException + { + checkShutdown(); + RegistryImpl.checkAccess( + "ActivationSystem.setActivationGroupDesc"); + + checkArgs(desc, null); + return getGroupEntry(id).setActivationGroupDesc(id, desc, true); + } + + public ActivationDesc getActivationDesc(ActivationID id) + throws ActivationException, UnknownObjectException, RemoteException + { + checkShutdown(); + RegistryImpl.checkAccess("ActivationSystem.getActivationDesc"); + + return getGroupEntry(id).getActivationDesc(id); + } + + public ActivationGroupDesc getActivationGroupDesc(ActivationGroupID id) + throws ActivationException, UnknownGroupException, RemoteException + { + checkShutdown(); + RegistryImpl.checkAccess + ("ActivationSystem.getActivationGroupDesc"); + + return getGroupEntry(id).desc; + } + + /** + * Shutdown the activation system. Destroys all groups spawned by + * the activation daemon and exits the activation daemon. + */ + public void shutdown() throws AccessException { + RegistryImpl.checkAccess("ActivationSystem.shutdown"); + + Object lock = startupLock; + if (lock != null) { + synchronized (lock) { + // nothing + } + } + + synchronized (Activation.this) { + if (!shuttingDown) { + shuttingDown = true; + (new Shutdown()).start(); + } + } + } + } + + private void checkShutdown() throws ActivationException { + // if the startup critical section is running, wait until it + // completes/fails before continuing with the remote call. + Object lock = startupLock; + if (lock != null) { + synchronized (lock) { + // nothing + } + } + + if (shuttingDown == true) { + throw new ActivationException( + "activation system shutting down"); + } + } + + private static void unexport(Remote obj) { + for (;;) { + try { + if (UnicastRemoteObject.unexportObject(obj, false) == true) { + break; + } else { + Thread.sleep(100); + } + } catch (Exception e) { + continue; + } + } + } + + /** + * Thread to shutdown rmid. + */ + private class Shutdown extends Thread { + Shutdown() { + super("rmid Shutdown"); + } + + public void run() { + try { + /* + * Unexport activation system services + */ + unexport(activator); + unexport(system); + + // destroy all child processes (groups) + GroupEntry[] groupEntries; + synchronized (groupTable) { + groupEntries = groupTable.values(). + toArray(new GroupEntry[groupTable.size()]); + } + for (GroupEntry groupEntry : groupEntries) { + groupEntry.shutdown(); + } + + Runtime.getRuntime().removeShutdownHook(shutdownHook); + + /* + * Unexport monitor safely since all processes are destroyed. + */ + unexport(monitor); + + /* + * Close log file, fix for 4243264: rmid shutdown thread + * interferes with remote calls in progress. Make sure + * the log file is only closed when it is impossible for + * its closure to interfere with any pending remote calls. + * We close the log when all objects in the rmid VM are + * unexported. + */ + try { + synchronized (log) { + log.close(); + } + } catch (IOException e) { + } + + } finally { + /* + * Now exit... A System.exit should only be done if + * the RMI activation system daemon was started up + * by the main method below (in which should always + * be the case since the Activation contructor is private). + */ + System.err.println(getTextResource("rmid.daemon.shutdown")); + System.exit(0); + } + } + } + + /** Thread to destroy children in the event of abnormal termination. */ + private class ShutdownHook extends Thread { + ShutdownHook() { + super("rmid ShutdownHook"); + } + + public void run() { + synchronized (Activation.this) { + shuttingDown = true; + } + + // destroy all child processes (groups) quickly + synchronized (groupTable) { + for (GroupEntry groupEntry : groupTable.values()) { + groupEntry.shutdownFast(); + } + } + } + } + + /** + * Returns the groupID for a given id of an object in the group. + * Throws UnknownObjectException if the object is not registered. + */ + private ActivationGroupID getGroupID(ActivationID id) + throws UnknownObjectException + { + synchronized (idTable) { + ActivationGroupID groupID = idTable.get(id); + if (groupID != null) { + return groupID; + } + } + throw new UnknownObjectException("unknown object: " + id); + } + + /** + * Returns the group entry for the group id. Throws + * UnknownGroupException if the group is not registered. + */ + private GroupEntry getGroupEntry(ActivationGroupID id) + throws UnknownGroupException + { + if (id.getClass() == ActivationGroupID.class) { + synchronized (groupTable) { + GroupEntry entry = groupTable.get(id); + if (entry != null && !entry.removed) { + return entry; + } + } + } + throw new UnknownGroupException("group unknown"); + } + + /** + * Returns the group entry for the object's id. Throws + * UnknownObjectException if the object is not registered or the + * object's group is not registered. + */ + private GroupEntry getGroupEntry(ActivationID id) + throws UnknownObjectException + { + ActivationGroupID gid = getGroupID(id); + synchronized (groupTable) { + GroupEntry entry = groupTable.get(gid); + if (entry != null) { + return entry; + } + } + throw new UnknownObjectException("object's group removed"); + } + + /** + * Container for group information: group's descriptor, group's + * instantiator, flag to indicate pending group creation, and + * table of the group's actived objects. + * + * WARNING: GroupEntry objects should not be written into log file + * updates. GroupEntrys are inner classes of Activation and they + * can not be serialized independent of this class. If the + * complete Activation system is written out as a log update, the + * point of having updates is nullified. + */ + private class GroupEntry implements Serializable { + + /** indicate compatibility with JDK 1.2 version of class */ + private static final long serialVersionUID = 7222464070032993304L; + private static final int MAX_TRIES = 2; + private static final int NORMAL = 0; + private static final int CREATING = 1; + private static final int TERMINATE = 2; + private static final int TERMINATING = 3; + + ActivationGroupDesc desc = null; + ActivationGroupID groupID = null; + long incarnation = 0; + Map<ActivationID,ObjectEntry> objects = + new HashMap<ActivationID,ObjectEntry>(); + Set<ActivationID> restartSet = new HashSet<ActivationID>(); + + transient ActivationInstantiator group = null; + transient int status = NORMAL; + transient long waitTime = 0; + transient String groupName = null; + transient Process child = null; + transient boolean removed = false; + transient Watchdog watchdog = null; + + GroupEntry(ActivationGroupID groupID, ActivationGroupDesc desc) { + this.groupID = groupID; + this.desc = desc; + } + + void restartServices() { + Iterator<ActivationID> iter = null; + + synchronized (this) { + if (restartSet.isEmpty()) { + return; + } + + /* + * Clone the restartSet so the set does not have to be locked + * during iteration. Locking the restartSet could cause + * deadlock if an object we are restarting caused another + * object in this group to be activated. + */ + iter = (new HashSet<ActivationID>(restartSet)).iterator(); + } + + while (iter.hasNext()) { + ActivationID id = iter.next(); + try { + activate(id, true); + } catch (Exception e) { + if (shuttingDown) { + return; + } + System.err.println( + getTextResource("rmid.restart.service.warning")); + e.printStackTrace(); + } + } + } + + synchronized void activeGroup(ActivationInstantiator inst, + long instIncarnation) + throws ActivationException, UnknownGroupException + { + if (incarnation != instIncarnation) { + throw new ActivationException("invalid incarnation"); + } + + if (group != null) { + if (group.equals(inst)) { + return; + } else { + throw new ActivationException("group already active"); + } + } + + if (child != null && status != CREATING) { + throw new ActivationException("group not being created"); + } + + group = inst; + status = NORMAL; + notifyAll(); + } + + private void checkRemoved() throws UnknownGroupException { + if (removed) { + throw new UnknownGroupException("group removed"); + } + } + + private ObjectEntry getObjectEntry(ActivationID id) + throws UnknownObjectException + { + if (removed) { + throw new UnknownObjectException("object's group removed"); + } + ObjectEntry objEntry = objects.get(id); + if (objEntry == null) { + throw new UnknownObjectException("object unknown"); + } + return objEntry; + } + + synchronized void registerObject(ActivationID id, + ActivationDesc desc, + boolean addRecord) + throws UnknownGroupException, ActivationException + { + checkRemoved(); + objects.put(id, new ObjectEntry(desc)); + if (desc.getRestartMode() == true) { + restartSet.add(id); + } + + // table insertion must take place before log update + synchronized (idTable) { + idTable.put(id, groupID); + } + + if (addRecord) { + addLogRecord(new LogRegisterObject(id, desc)); + } + } + + synchronized void unregisterObject(ActivationID id, boolean addRecord) + throws UnknownGroupException, ActivationException + { + ObjectEntry objEntry = getObjectEntry(id); + objEntry.removed = true; + objects.remove(id); + if (objEntry.desc.getRestartMode() == true) { + restartSet.remove(id); + } + + // table insertion must take place before log update + synchronized (idTable) { + idTable.remove(id); + } + if (addRecord) { + addLogRecord(new LogUnregisterObject(id)); + } + } + + synchronized void unregisterGroup(boolean addRecord) + throws UnknownGroupException, ActivationException + { + checkRemoved(); + removed = true; + for (Map.Entry<ActivationID,ObjectEntry> entry : + objects.entrySet()) + { + ActivationID id = entry.getKey(); + synchronized (idTable) { + idTable.remove(id); + } + ObjectEntry objEntry = entry.getValue(); + objEntry.removed = true; + } + objects.clear(); + restartSet.clear(); + reset(); + childGone(); + + // removal should be recorded before log update + if (addRecord) { + addLogRecord(new LogUnregisterGroup(groupID)); + } + } + + synchronized ActivationDesc setActivationDesc(ActivationID id, + ActivationDesc desc, + boolean addRecord) + throws UnknownObjectException, UnknownGroupException, + ActivationException + { + ObjectEntry objEntry = getObjectEntry(id); + ActivationDesc oldDesc = objEntry.desc; + objEntry.desc = desc; + if (desc.getRestartMode() == true) { + restartSet.add(id); + } else { + restartSet.remove(id); + } + // restart information should be recorded before log update + if (addRecord) { + addLogRecord(new LogUpdateDesc(id, desc)); + } + + return oldDesc; + } + + synchronized ActivationDesc getActivationDesc(ActivationID id) + throws UnknownObjectException, UnknownGroupException + { + return getObjectEntry(id).desc; + } + + synchronized ActivationGroupDesc setActivationGroupDesc( + ActivationGroupID id, + ActivationGroupDesc desc, + boolean addRecord) + throws UnknownGroupException, ActivationException + { + checkRemoved(); + ActivationGroupDesc oldDesc = this.desc; + this.desc = desc; + // state update should occur before log update + if (addRecord) { + addLogRecord(new LogUpdateGroupDesc(id, desc)); + } + return oldDesc; + } + + synchronized void inactiveGroup(long incarnation, boolean failure) + throws UnknownGroupException + { + checkRemoved(); + if (this.incarnation != incarnation) { + throw new UnknownGroupException("invalid incarnation"); + } + + reset(); + if (failure) { + terminate(); + } else if (child != null && status == NORMAL) { + status = TERMINATE; + watchdog.noRestart(); + } + } + + synchronized void activeObject(ActivationID id, + MarshalledObject<? extends Remote> mobj) + throws UnknownObjectException + { + getObjectEntry(id).stub = mobj; + } + + synchronized void inactiveObject(ActivationID id) + throws UnknownObjectException + { + getObjectEntry(id).reset(); + } + + private synchronized void reset() { + group = null; + for (ObjectEntry objectEntry : objects.values()) { + objectEntry.reset(); + } + } + + private void childGone() { + if (child != null) { + child = null; + watchdog.dispose(); + watchdog = null; + status = NORMAL; + notifyAll(); + } + } + + private void terminate() { + if (child != null && status != TERMINATING) { + child.destroy(); + status = TERMINATING; + waitTime = System.currentTimeMillis() + groupTimeout; + notifyAll(); + } + } + + private void await() { + while (true) { + switch (status) { + case NORMAL: + return; + case TERMINATE: + terminate(); + case TERMINATING: + try { + child.exitValue(); + } catch (IllegalThreadStateException e) { + long now = System.currentTimeMillis(); + if (waitTime > now) { + try { + wait(waitTime - now); + } catch (InterruptedException ee) { + } + continue; + } + // REMIND: print message that group did not terminate? + } + childGone(); + return; + case CREATING: + try { + wait(); + } catch (InterruptedException e) { + } + } + } + } + + // no synchronization to avoid delay wrt getInstantiator + void shutdownFast() { + Process p = child; + if (p != null) { + p.destroy(); + } + } + + synchronized void shutdown() { + reset(); + terminate(); + await(); + } + + MarshalledObject<? extends Remote> activate(ActivationID id, + boolean force) + throws ActivationException + { + Exception detail = null; + + /* + * Attempt to activate object and reattempt (several times) + * if activation fails due to communication problems. + */ + for (int tries = MAX_TRIES; tries > 0; tries--) { + ActivationInstantiator inst; + long currentIncarnation; + + // look up object to activate + ObjectEntry objEntry; + synchronized (this) { + objEntry = getObjectEntry(id); + // if not forcing activation, return cached stub + if (!force && objEntry.stub != null) { + return objEntry.stub; + } + inst = getInstantiator(groupID); + currentIncarnation = incarnation; + } + + boolean groupInactive = false; + boolean failure = false; + // activate object + try { + return objEntry.activate(id, force, inst); + } catch (NoSuchObjectException e) { + groupInactive = true; + detail = e; + } catch (ConnectException e) { + groupInactive = true; + failure = true; + detail = e; + } catch (ConnectIOException e) { + groupInactive = true; + failure = true; + detail = e; + } catch (InactiveGroupException e) { + groupInactive = true; + detail = e; + } catch (RemoteException e) { + // REMIND: wait some here before continuing? + if (detail == null) { + detail = e; + } + } + + if (groupInactive) { + // group has failed or is inactive; mark inactive + try { + System.err.println( + MessageFormat.format( + getTextResource("rmid.group.inactive"), + detail.toString())); + detail.printStackTrace(); + getGroupEntry(groupID). + inactiveGroup(currentIncarnation, failure); + } catch (UnknownGroupException e) { + // not a problem + } + } + } + + /** + * signal that group activation failed, nested exception + * specifies what exception occurred when the group did not + * activate + */ + throw new ActivationException("object activation failed after " + + MAX_TRIES + " tries", detail); + } + + /** + * Returns the instantiator for the group specified by id and + * entry. If the group is currently inactive, exec some + * bootstrap code to create the group. + */ + private ActivationInstantiator getInstantiator(ActivationGroupID id) + throws ActivationException + { + assert Thread.holdsLock(this); + + await(); + if (group != null) { + return group; + } + checkRemoved(); + boolean acquired = false; + + try { + groupName = Pstartgroup(); + acquired = true; + String[] argv = activationArgs(desc); + checkArgs(desc, argv); + + if (debugExec) { + StringBuffer sb = new StringBuffer(argv[0]); + int j; + for (j = 1; j < argv.length; j++) { + sb.append(' '); + sb.append(argv[j]); + } + System.err.println( + MessageFormat.format( + getTextResource("rmid.exec.command"), + sb.toString())); + } + + try { + child = Runtime.getRuntime().exec(argv); + status = CREATING; + ++incarnation; + watchdog = new Watchdog(); + watchdog.start(); + addLogRecord(new LogGroupIncarnation(id, incarnation)); + + // handle child I/O streams before writing to child + PipeWriter.plugTogetherPair + (child.getInputStream(), System.out, + child.getErrorStream(), System.err); + + MarshalOutputStream out = + new MarshalOutputStream(child.getOutputStream()); + out.writeObject(id); + out.writeObject(desc); + out.writeLong(incarnation); + out.flush(); + out.close(); + + + } catch (IOException e) { + terminate(); + throw new ActivationException( + "unable to create activation group", e); + } + + try { + long now = System.currentTimeMillis(); + long stop = now + execTimeout; + do { + wait(stop - now); + if (group != null) { + return group; + } + now = System.currentTimeMillis(); + } while (status == CREATING && now < stop); + } catch (InterruptedException e) { + } + + terminate(); + throw new ActivationException( + (removed ? + "activation group unregistered" : + "timeout creating child process")); + } finally { + if (acquired) { + Vstartgroup(); + } + } + } + + /** + * Waits for process termination and then restarts services. + */ + private class Watchdog extends Thread { + private final Process groupProcess = child; + private final long groupIncarnation = incarnation; + private boolean canInterrupt = true; + private boolean shouldQuit = false; + private boolean shouldRestart = true; + + Watchdog() { + super("WatchDog-" + groupName + "-" + incarnation); + setDaemon(true); + } + + public void run() { + + if (shouldQuit) { + return; + } + + /* + * Wait for the group to crash or exit. + */ + try { + groupProcess.waitFor(); + } catch (InterruptedException exit) { + return; + } + + boolean restart = false; + synchronized (GroupEntry.this) { + if (shouldQuit) { + return; + } + canInterrupt = false; + interrupted(); // clear interrupt bit + /* + * Since the group crashed, we should + * reset the entry before activating objects + */ + if (groupIncarnation == incarnation) { + restart = shouldRestart && !shuttingDown; + reset(); + childGone(); + } + } + + /* + * Activate those objects that require restarting + * after a crash. + */ + if (restart) { + restartServices(); + } + } + + /** + * Marks this thread as one that is no longer needed. + * If the thread is in a state in which it can be interrupted, + * then the thread is interrupted. + */ + void dispose() { + shouldQuit = true; + if (canInterrupt) { + interrupt(); + } + } + + /** + * Marks this thread as no longer needing to restart objects. + */ + void noRestart() { + shouldRestart = false; + } + } + } + + private String[] activationArgs(ActivationGroupDesc desc) { + ActivationGroupDesc.CommandEnvironment cmdenv; + cmdenv = desc.getCommandEnvironment(); + + // argv is the literal command to exec + List<String> argv = new ArrayList<String>(); + + // Command name/path + argv.add((cmdenv != null && cmdenv.getCommandPath() != null) + ? cmdenv.getCommandPath() + : command[0]); + + // Group-specific command options + if (cmdenv != null && cmdenv.getCommandOptions() != null) { + argv.addAll(Arrays.asList(cmdenv.getCommandOptions())); + } + + // Properties become -D parameters + Properties props = desc.getPropertyOverrides(); + if (props != null) { + for (Enumeration<?> p = props.propertyNames(); + p.hasMoreElements();) + { + String name = (String) p.nextElement(); + /* Note on quoting: it would be wrong + * here, since argv will be passed to + * Runtime.exec, which should not parse + * arguments or split on whitespace. + */ + argv.add("-D" + name + "=" + props.getProperty(name)); + } + } + + /* Finally, rmid-global command options (e.g. -C options) + * and the classname + */ + for (int i = 1; i < command.length; i++) { + argv.add(command[i]); + } + + String[] realArgv = new String[argv.size()]; + System.arraycopy(argv.toArray(), 0, realArgv, 0, realArgv.length); + + return realArgv; + } + + private void checkArgs(ActivationGroupDesc desc, String[] cmd) + throws SecurityException, ActivationException + { + /* + * Check exec command using execPolicy object + */ + if (execPolicyMethod != null) { + if (cmd == null) { + cmd = activationArgs(desc); + } + try { + execPolicyMethod.invoke(execPolicy, desc, cmd); + } catch (InvocationTargetException e) { + Throwable targetException = e.getTargetException(); + if (targetException instanceof SecurityException) { + throw (SecurityException) targetException; + } else { + throw new ActivationException( + execPolicyMethod.getName() + ": unexpected exception", + e); + } + } catch (Exception e) { + throw new ActivationException( + execPolicyMethod.getName() + ": unexpected exception", e); + } + } + } + + private static class ObjectEntry implements Serializable { + + private static final long serialVersionUID = -5500114225321357856L; + + /** descriptor for object */ + ActivationDesc desc; + /** the stub (if active) */ + volatile transient MarshalledObject<? extends Remote> stub = null; + volatile transient boolean removed = false; + + ObjectEntry(ActivationDesc desc) { + this.desc = desc; + } + + synchronized MarshalledObject<? extends Remote> + activate(ActivationID id, + boolean force, + ActivationInstantiator inst) + throws RemoteException, ActivationException + { + MarshalledObject<? extends Remote> nstub = stub; + if (removed) { + throw new UnknownObjectException("object removed"); + } else if (!force && nstub != null) { + return nstub; + } + + nstub = inst.newInstance(id, desc); + stub = nstub; + /* + * stub could be set to null by a group reset, so return + * the newstub here to prevent returning null. + */ + return nstub; + } + + void reset() { + stub = null; + } + } + + /** + * Add a record to the activation log. If the number of updates + * passes a predetermined threshold, record a snapshot. + */ + private void addLogRecord(LogRecord rec) throws ActivationException { + synchronized (log) { + checkShutdown(); + try { + log.update(rec, true); + } catch (Exception e) { + numUpdates = snapshotInterval; + System.err.println(getTextResource("rmid.log.update.warning")); + e.printStackTrace(); + } + if (++numUpdates < snapshotInterval) { + return; + } + try { + log.snapshot(this); + numUpdates = 0; + } catch (Exception e) { + System.err.println( + getTextResource("rmid.log.snapshot.warning")); + e.printStackTrace(); + try { + // shutdown activation system because snapshot failed + system.shutdown(); + } catch (RemoteException ignore) { + // can't happen + } + // warn the client of the original update problem + throw new ActivationException("log snapshot failed", e); + } + } + } + + /** + * Handler for the log that knows how to take the initial snapshot + * and apply an update (a LogRecord) to the current state. + */ + private static class ActLogHandler extends LogHandler { + + ActLogHandler() { + } + + public Object initialSnapshot() + { + /** + * Return an empty Activation object. Log will update + * this object with recovered state. + */ + return new Activation(); + } + + public Object applyUpdate(Object update, Object state) + throws Exception + { + return ((LogRecord) update).apply(state); + } + + } + + /** + * Abstract class for all log records. The subclass contains + * specific update information and implements the apply method + * that applys the update information contained in the record + * to the current state. + */ + private static abstract class LogRecord implements Serializable { + /** indicate compatibility with JDK 1.2 version of class */ + private static final long serialVersionUID = 8395140512322687529L; + abstract Object apply(Object state) throws Exception; + } + + /** + * Log record for registering an object. + */ + private static class LogRegisterObject extends LogRecord { + /** indicate compatibility with JDK 1.2 version of class */ + private static final long serialVersionUID = -6280336276146085143L; + private ActivationID id; + private ActivationDesc desc; + + LogRegisterObject(ActivationID id, ActivationDesc desc) { + this.id = id; + this.desc = desc; + } + + Object apply(Object state) { + try { + ((Activation) state).getGroupEntry(desc.getGroupID()). + registerObject(id, desc, false); + } catch (Exception ignore) { + System.err.println( + MessageFormat.format( + getTextResource("rmid.log.recover.warning"), + "LogRegisterObject")); + ignore.printStackTrace(); + } + return state; + } + } + + /** + * Log record for unregistering an object. + */ + private static class LogUnregisterObject extends LogRecord { + /** indicate compatibility with JDK 1.2 version of class */ + private static final long serialVersionUID = 6269824097396935501L; + private ActivationID id; + + LogUnregisterObject(ActivationID id) { + this.id = id; + } + + Object apply(Object state) { + try { + ((Activation) state).getGroupEntry(id). + unregisterObject(id, false); + } catch (Exception ignore) { + System.err.println( + MessageFormat.format( + getTextResource("rmid.log.recover.warning"), + "LogUnregisterObject")); + ignore.printStackTrace(); + } + return state; + } + } + + /** + * Log record for registering a group. + */ + private static class LogRegisterGroup extends LogRecord { + /** indicate compatibility with JDK 1.2 version of class */ + private static final long serialVersionUID = -1966827458515403625L; + private ActivationGroupID id; + private ActivationGroupDesc desc; + + LogRegisterGroup(ActivationGroupID id, ActivationGroupDesc desc) { + this.id = id; + this.desc = desc; + } + + Object apply(Object state) { + // modify state directly; cant ask a nonexistent GroupEntry + // to register itself. + ((Activation) state).groupTable.put(id, ((Activation) state).new + GroupEntry(id, desc)); + return state; + } + } + + /** + * Log record for udpating an activation desc + */ + private static class LogUpdateDesc extends LogRecord { + /** indicate compatibility with JDK 1.2 version of class */ + private static final long serialVersionUID = 545511539051179885L; + + private ActivationID id; + private ActivationDesc desc; + + LogUpdateDesc(ActivationID id, ActivationDesc desc) { + this.id = id; + this.desc = desc; + } + + Object apply(Object state) { + try { + ((Activation) state).getGroupEntry(id). + setActivationDesc(id, desc, false); + } catch (Exception ignore) { + System.err.println( + MessageFormat.format( + getTextResource("rmid.log.recover.warning"), + "LogUpdateDesc")); + ignore.printStackTrace(); + } + return state; + } + } + + /** + * Log record for unregistering a group. + */ + private static class LogUpdateGroupDesc extends LogRecord { + /** indicate compatibility with JDK 1.2 version of class */ + private static final long serialVersionUID = -1271300989218424337L; + private ActivationGroupID id; + private ActivationGroupDesc desc; + + LogUpdateGroupDesc(ActivationGroupID id, ActivationGroupDesc desc) { + this.id = id; + this.desc = desc; + } + + Object apply(Object state) { + try { + ((Activation) state).getGroupEntry(id). + setActivationGroupDesc(id, desc, false); + } catch (Exception ignore) { + System.err.println( + MessageFormat.format( + getTextResource("rmid.log.recover.warning"), + "LogUpdateGroupDesc")); + ignore.printStackTrace(); + } + return state; + } + } + + /** + * Log record for unregistering a group. + */ + private static class LogUnregisterGroup extends LogRecord { + /** indicate compatibility with JDK 1.2 version of class */ + private static final long serialVersionUID = -3356306586522147344L; + private ActivationGroupID id; + + LogUnregisterGroup(ActivationGroupID id) { + this.id = id; + } + + Object apply(Object state) { + GroupEntry entry = ((Activation) state).groupTable.remove(id); + try { + entry.unregisterGroup(false); + } catch (Exception ignore) { + System.err.println( + MessageFormat.format( + getTextResource("rmid.log.recover.warning"), + "LogUnregisterGroup")); + ignore.printStackTrace(); + } + return state; + } + } + + /** + * Log record for an active group incarnation + */ + private static class LogGroupIncarnation extends LogRecord { + /** indicate compatibility with JDK 1.2 version of class */ + private static final long serialVersionUID = 4146872747377631897L; + private ActivationGroupID id; + private long inc; + + LogGroupIncarnation(ActivationGroupID id, long inc) { + this.id = id; + this.inc = inc; + } + + Object apply(Object state) { + try { + GroupEntry entry = ((Activation) state).getGroupEntry(id); + entry.incarnation = inc; + } catch (Exception ignore) { + System.err.println( + MessageFormat.format( + getTextResource("rmid.log.recover.warning"), + "LogGroupIncarnation")); + ignore.printStackTrace(); + } + return state; + } + } + + /** + * Initialize command to exec a default group. + */ + private void initCommand(String[] childArgs) { + command = new String[childArgs.length + 2]; + AccessController.doPrivileged(new PrivilegedAction<Void>() { + public Void run() { + try { + command[0] = System.getProperty("java.home") + + File.separator + "bin" + File.separator + "java"; + } catch (Exception e) { + System.err.println( + getTextResource("rmid.unfound.java.home.property")); + command[0] = "java"; + } + return null; + } + }); + System.arraycopy(childArgs, 0, command, 1, childArgs.length); + command[command.length-1] = "sun.rmi.server.ActivationGroupInit"; + } + + private static void bomb(String error) { + System.err.println("rmid: " + error); // $NON-NLS$ + System.err.println(MessageFormat.format(getTextResource("rmid.usage"), + "rmid")); + System.exit(1); + } + + /** + * The default policy for checking a command before it is executed + * makes sure the appropriate com.sun.rmi.rmid.ExecPermission and + * set of com.sun.rmi.rmid.ExecOptionPermissions have been granted. + */ + public static class DefaultExecPolicy { + + public void checkExecCommand(ActivationGroupDesc desc, String[] cmd) + throws SecurityException + { + PermissionCollection perms = getExecPermissions(); + + /* + * Check properties overrides. + */ + Properties props = desc.getPropertyOverrides(); + if (props != null) { + Enumeration<?> p = props.propertyNames(); + while (p.hasMoreElements()) { + String name = (String) p.nextElement(); + String value = props.getProperty(name); + String option = "-D" + name + "=" + value; + try { + checkPermission(perms, + new ExecOptionPermission(option)); + } catch (AccessControlException e) { + if (value.equals("")) { + checkPermission(perms, + new ExecOptionPermission("-D" + name)); + } else { + throw e; + } + } + } + } + + /* + * Check group class name (allow nothing but the default), + * code location (must be null), and data (must be null). + */ + String groupClassName = desc.getClassName(); + if ((groupClassName != null && + !groupClassName.equals( + ActivationGroupImpl.class.getName())) || + (desc.getLocation() != null) || + (desc.getData() != null)) + { + throw new AccessControlException( + "access denied (custom group implementation not allowed)"); + } + + /* + * If group descriptor has a command environment, check + * command and options. + */ + ActivationGroupDesc.CommandEnvironment cmdenv; + cmdenv = desc.getCommandEnvironment(); + if (cmdenv != null) { + String path = cmdenv.getCommandPath(); + if (path != null) { + checkPermission(perms, new ExecPermission(path)); + } + + String[] options = cmdenv.getCommandOptions(); + if (options != null) { + for (String option : options) { + checkPermission(perms, + new ExecOptionPermission(option)); + } + } + } + } + + /** + * Prints warning message if installed Policy is the default Policy + * implementation and globally granted permissions do not include + * AllPermission or any ExecPermissions/ExecOptionPermissions. + */ + static void checkConfiguration() { + Policy policy = + AccessController.doPrivileged(new PrivilegedAction<Policy>() { + public Policy run() { + return Policy.getPolicy(); + } + }); + if (!(policy instanceof PolicyFile)) { + return; + } + PermissionCollection perms = getExecPermissions(); + for (Enumeration<Permission> e = perms.elements(); + e.hasMoreElements();) + { + Permission p = e.nextElement(); + if (p instanceof AllPermission || + p instanceof ExecPermission || + p instanceof ExecOptionPermission) + { + return; + } + } + System.err.println(getTextResource("rmid.exec.perms.inadequate")); + } + + private static PermissionCollection getExecPermissions() { + /* + * The approach used here is taken from the similar method + * getLoaderAccessControlContext() in the class + * sun.rmi.server.LoaderHandler. + */ + + // obtain permissions granted to all code in current policy + PermissionCollection perms = AccessController.doPrivileged( + new PrivilegedAction<PermissionCollection>() { + public PermissionCollection run() { + CodeSource codesource = + new CodeSource(null, (Certificate[]) null); + Policy p = Policy.getPolicy(); + if (p != null) { + return p.getPermissions(codesource); + } else { + return new Permissions(); + } + } + }); + + return perms; + } + + private static void checkPermission(PermissionCollection perms, + Permission p) + throws AccessControlException + { + if (!perms.implies(p)) { + throw new AccessControlException( + "access denied " + p.toString()); + } + } + } + + /** + * Main program to start the activation system. <br> + * The usage is as follows: rmid [-port num] [-log dir]. + */ + public static void main(String[] args) { + boolean stop = false; + + // Create and install the security manager if one is not installed + // already. + if (System.getSecurityManager() == null) { + System.setSecurityManager(new SecurityManager()); + } + + try { + int port = ActivationSystem.SYSTEM_PORT; + RMIServerSocketFactory ssf = null; + + /* + * If rmid has an inherited channel (meaning that it was + * launched from inetd), set the server socket factory to + * return the inherited server socket. + **/ + Channel inheritedChannel = AccessController.doPrivileged( + new PrivilegedExceptionAction<Channel>() { + public Channel run() throws IOException { + return System.inheritedChannel(); + } + }); + + if (inheritedChannel != null && + inheritedChannel instanceof ServerSocketChannel) + { + /* + * Redirect System.err output to a file. + */ + AccessController.doPrivileged( + new PrivilegedExceptionAction<Void>() { + public Void run() throws IOException { + File file = + File.createTempFile("rmid-err", null, null); + PrintStream errStream = + new PrintStream(new FileOutputStream(file)); + System.setErr(errStream); + return null; + } + }); + + ServerSocket serverSocket = + ((ServerSocketChannel) inheritedChannel).socket(); + port = serverSocket.getLocalPort(); + ssf = new ActivationServerSocketFactory(serverSocket); + + System.err.println(new Date()); + System.err.println(getTextResource( + "rmid.inherited.channel.info") + + ": " + inheritedChannel); + } + + String log = null; + List<String> childArgs = new ArrayList<String>(); + + /* + * Parse arguments + */ + for (int i = 0; i < args.length; i++) { + if (args[i].equals("-port")) { + if (ssf != null) { + bomb(getTextResource("rmid.syntax.port.badarg")); + } + if ((i + 1) < args.length) { + try { + port = Integer.parseInt(args[++i]); + } catch (NumberFormatException nfe) { + bomb(getTextResource("rmid.syntax.port.badnumber")); + } + } else { + bomb(getTextResource("rmid.syntax.port.missing")); + } + + } else if (args[i].equals("-log")) { + if ((i + 1) < args.length) { + log = args[++i]; + } else { + bomb(getTextResource("rmid.syntax.log.missing")); + } + + } else if (args[i].equals("-stop")) { + stop = true; + + } else if (args[i].startsWith("-C")) { + childArgs.add(args[i].substring(2)); + + } else { + bomb(MessageFormat.format( + getTextResource("rmid.syntax.illegal.option"), + args[i])); + } + } + + if (log == null) { + if (ssf != null) { + bomb(getTextResource("rmid.syntax.log.required")); + } else { + log = "log"; + } + } + + debugExec = AccessController.doPrivileged( + new GetBooleanAction("sun.rmi.server.activation.debugExec")); + + /** + * Determine class name for activation exec policy (if any). + */ + String execPolicyClassName = AccessController.doPrivileged( + new GetPropertyAction("sun.rmi.activation.execPolicy", null)); + if (execPolicyClassName == null) { + if (!stop) { + DefaultExecPolicy.checkConfiguration(); + } + execPolicyClassName = "default"; + } + + /** + * Initialize method for activation exec policy. + */ + if (!execPolicyClassName.equals("none")) { + if (execPolicyClassName.equals("") || + execPolicyClassName.equals("default")) + { + execPolicyClassName = DefaultExecPolicy.class.getName(); + } + + try { + Class<?> execPolicyClass = + RMIClassLoader.loadClass(execPolicyClassName); + execPolicy = execPolicyClass.newInstance(); + execPolicyMethod = + execPolicyClass.getMethod("checkExecCommand", + ActivationGroupDesc.class, + String[].class); + } catch (Exception e) { + if (debugExec) { + System.err.println( + getTextResource("rmid.exec.policy.exception")); + e.printStackTrace(); + } + bomb(getTextResource("rmid.exec.policy.invalid")); + } + } + + if (stop == true) { + final int finalPort = port; + AccessController.doPrivileged(new PrivilegedAction<Void>() { + public Void run() { + System.setProperty("java.rmi.activation.port", + Integer.toString(finalPort)); + return null; + } + }); + ActivationSystem system = ActivationGroup.getSystem(); + system.shutdown(); + System.exit(0); + } + + /* + * Fix for 4173960: Create and initialize activation using + * a static method, startActivation, which will build the + * Activation state in two ways: if when rmid is run, no + * log file is found, the ActLogHandler.recover(...) + * method will create a new Activation instance. + * Alternatively, if a logfile is available, a serialized + * instance of activation will be read from the log's + * snapshot file. Log updates will be applied to this + * Activation object until rmid's state has been fully + * recovered. In either case, only one instance of + * Activation is created. + */ + startActivation(port, ssf, log, + childArgs.toArray(new String[childArgs.size()])); + + // prevent activator from exiting + while (true) { + try { + Thread.sleep(Long.MAX_VALUE); + } catch (InterruptedException e) { + } + } + } catch (Exception e) { + System.err.println( + MessageFormat.format( + getTextResource("rmid.unexpected.exception"), e)); + e.printStackTrace(); + } + System.exit(1); + } + + /** + * Retrieves text resources from the locale-specific properties file. + */ + private static String getTextResource(String key) { + if (Activation.resources == null) { + try { + Activation.resources = ResourceBundle.getBundle( + "sun.rmi.server.resources.rmid"); + } catch (MissingResourceException mre) { + } + if (Activation.resources == null) { + // throwing an Error is a bit extreme, methinks + return ("[missing resource file: " + key + "]"); + } + } + + String val = null; + try { + val = Activation.resources.getString (key); + } catch (MissingResourceException mre) { + } + + if (val == null) { + return ("[missing resource: " + key + "]"); + } else { + return val; + } + } + + /* + * Dijkstra semaphore operations to limit the number of subprocesses + * rmid attempts to make at once. + */ + /** + * Acquire the group semaphore and return a group name. Each + * Pstartgroup must be followed by a Vstartgroup. The calling thread + * will wait until there are fewer than <code>N</code> other threads + * holding the group semaphore. The calling thread will then acquire + * the semaphore and return. + */ + private synchronized String Pstartgroup() throws ActivationException { + while (true) { + checkShutdown(); + // Wait until positive, then decrement. + if (groupSemaphore > 0) { + groupSemaphore--; + return "Group-" + groupCounter++; + } + + try { + wait(); + } catch (InterruptedException e) { + } + } + } + + /** + * Release the group semaphore. Every P operation must be + * followed by a V operation. This may cause another thread to + * wake up and return from its P operation. + */ + private synchronized void Vstartgroup() { + // Increment and notify a waiter (not necessarily FIFO). + groupSemaphore++; + notifyAll(); + } + + /** + * A server socket factory to use when rmid is launched via 'inetd' + * with 'wait' status. This socket factory's 'createServerSocket' + * method returns the server socket specified during construction that + * is specialized to delay accepting requests until the + * 'initDone' flag is 'true'. The server socket supplied to + * the constructor should be the server socket obtained from the + * ServerSocketChannel returned from the 'System.inheritedChannel' + * method. + **/ + private static class ActivationServerSocketFactory + implements RMIServerSocketFactory + { + private final ServerSocket serverSocket; + + /** + * Constructs an 'ActivationServerSocketFactory' with the specified + * 'serverSocket'. + **/ + ActivationServerSocketFactory(ServerSocket serverSocket) { + this.serverSocket = serverSocket; + } + + /** + * Returns the server socket specified during construction wrapped + * in a 'DelayedAcceptServerSocket'. + **/ + public ServerSocket createServerSocket(int port) + throws IOException + { + return new DelayedAcceptServerSocket(serverSocket); + } + + } + + /** + * A server socket that delegates all public methods to the underlying + * server socket specified at construction. The accept method is + * overridden to delay calling accept on the underlying server socket + * until the 'initDone' flag is 'true'. + **/ + private static class DelayedAcceptServerSocket extends ServerSocket { + + private final ServerSocket serverSocket; + + DelayedAcceptServerSocket(ServerSocket serverSocket) + throws IOException + { + this.serverSocket = serverSocket; + } + + public void bind(SocketAddress endpoint) throws IOException { + serverSocket.bind(endpoint); + } + + public void bind(SocketAddress endpoint, int backlog) + throws IOException + { + serverSocket.bind(endpoint, backlog); + } + + public InetAddress getInetAddress() { + return serverSocket.getInetAddress(); + } + + public int getLocalPort() { + return serverSocket.getLocalPort(); + } + + public SocketAddress getLocalSocketAddress() { + return serverSocket.getLocalSocketAddress(); + } + + /** + * Delays calling accept on the underlying server socket until the + * remote service is bound in the registry. + **/ + public Socket accept() throws IOException { + synchronized (initLock) { + try { + while (!initDone) { + initLock.wait(); + } + } catch (InterruptedException ignore) { + throw new AssertionError(ignore); + } + } + return serverSocket.accept(); + } + + public void close() throws IOException { + serverSocket.close(); + } + + public ServerSocketChannel getChannel() { + return serverSocket.getChannel(); + } + + public boolean isBound() { + return serverSocket.isBound(); + } + + public boolean isClosed() { + return serverSocket.isClosed(); + } + + public void setSoTimeout(int timeout) + throws SocketException + { + serverSocket.setSoTimeout(timeout); + } + + public int getSoTimeout() throws IOException { + return serverSocket.getSoTimeout(); + } + + public void setReuseAddress(boolean on) throws SocketException { + serverSocket.setReuseAddress(on); + } + + public boolean getReuseAddress() throws SocketException { + return serverSocket.getReuseAddress(); + } + + public String toString() { + return serverSocket.toString(); + } + + public void setReceiveBufferSize(int size) + throws SocketException + { + serverSocket.setReceiveBufferSize(size); + } + + public int getReceiveBufferSize() + throws SocketException + { + return serverSocket.getReceiveBufferSize(); + } + } +} + +/** + * PipeWriter plugs together two pairs of input and output streams by + * providing readers for input streams and writing through to + * appropriate output streams. Both output streams are annotated on a + * per-line basis. + * + * @author Laird Dornin, much code borrowed from Peter Jones, Ken + * Arnold and Ann Wollrath. + */ +class PipeWriter implements Runnable { + + /** stream used for buffering lines */ + private ByteArrayOutputStream bufOut; + + /** count since last separator */ + private int cLast; + + /** current chunk of input being compared to lineSeparator.*/ + private byte[] currSep; + + private PrintWriter out; + private InputStream in; + + private String pipeString; + private String execString; + + private static String lineSeparator; + private static int lineSeparatorLength; + + private static int numExecs = 0; + + static { + lineSeparator = AccessController.doPrivileged( + new GetPropertyAction("line.separator")); + lineSeparatorLength = lineSeparator.length(); + } + + /** + * Create a new PipeWriter object. All methods of PipeWriter, + * except plugTogetherPair, are only accesible to PipeWriter + * itself. Synchronization is unnecessary on functions that will + * only be used internally in PipeWriter. + * + * @param in input stream from which pipe input flows + * @param out output stream to which log messages will be sent + * @param dest String which tags output stream as 'out' or 'err' + * @param nExecs number of execed processes, Activation groups. + */ + private PipeWriter + (InputStream in, OutputStream out, String tag, int nExecs) { + + this.in = in; + this.out = new PrintWriter(out); + + bufOut = new ByteArrayOutputStream(); + currSep = new byte[lineSeparatorLength]; + + /* set unique pipe/pair annotations */ + execString = ":ExecGroup-" + + Integer.toString(nExecs) + ':' + tag + ':'; + } + + /** + * Create a thread to listen and read from input stream, in. buffer + * the data that is read until a marker which equals lineSeparator + * is read. Once such a string has been discovered; write out an + * annotation string followed by the buffered data and a line + * separator. + */ + public void run() { + byte[] buf = new byte[256]; + int count; + + try { + /* read bytes till there are no more. */ + while ((count = in.read(buf)) != -1) { + write(buf, 0, count); + } + + /* flush internal buffer... may not have ended on a line + * separator, we also need a last annotation if + * something was left. + */ + String lastInBuffer = bufOut.toString(); + bufOut.reset(); + if (lastInBuffer.length() > 0) { + out.println (createAnnotation() + lastInBuffer); + out.flush(); // add a line separator + // to make output nicer + } + + } catch (IOException e) { + } + } + + /** + * Write a subarray of bytes. Pass each through write byte method. + */ + private void write(byte b[], int off, int len) throws IOException { + + if (len < 0) { + throw new ArrayIndexOutOfBoundsException(len); + } + for (int i = 0; i < len; ++ i) { + write(b[off + i]); + } + } + + /** + * Write a byte of data to the stream. If we have not matched a + * line separator string, then the byte is appended to the internal + * buffer. If we have matched a line separator, then the currently + * buffered line is sent to the output writer with a prepended + * annotation string. + */ + private void write(byte b) throws IOException { + int i = 0; + + /* shift current to the left */ + for (i = 1 ; i < (currSep.length); i ++) { + currSep[i-1] = currSep[i]; + } + currSep[i-1] = b; + bufOut.write(b); + + /* enough characters for a separator? */ + if ( (cLast >= (lineSeparatorLength - 1)) && + (lineSeparator.equals(new String(currSep))) ) { + + cLast = 0; + + /* write prefix through to underlying byte stream */ + out.print(createAnnotation() + bufOut.toString()); + out.flush(); + bufOut.reset(); + + if (out.checkError()) { + throw new IOException + ("PipeWriter: IO Exception when"+ + " writing to output stream."); + } + + } else { + cLast++; + } + } + + /** + * Create an annotation string to be printed out after + * a new line and end of stream. + */ + private String createAnnotation() { + + /* construct prefix for log messages: + * date/time stamp... + */ + return ((new Date()).toString() + + /* ... print pair # ... */ + (execString)); + } + + /** + * Allow plugging together two pipes at a time, to associate + * output from an execed process. This is the only publicly + * accessible method of this object; this helps ensure that + * synchronization will not be an issue in the annotation + * process. + * + * @param in input stream from which pipe input comes + * @param out output stream to which log messages will be sent + * @param in1 input stream from which pipe input comes + * @param out1 output stream to which log messages will be sent + */ + static void plugTogetherPair(InputStream in, + OutputStream out, + InputStream in1, + OutputStream out1) { + Thread inThread = null; + Thread outThread = null; + + int nExecs = getNumExec(); + + /* start RMI threads to read output from child process */ + inThread = AccessController.doPrivileged( + new NewThreadAction(new PipeWriter(in, out, "out", nExecs), + "out", true)); + outThread = AccessController.doPrivileged( + new NewThreadAction(new PipeWriter(in1, out1, "err", nExecs), + "err", true)); + inThread.start(); + outThread.start(); + } + + private static synchronized int getNumExec() { + return numExecs++; + } +} diff --git a/src/share/classes/sun/rmi/server/ActivationGroupImpl.java b/src/share/classes/sun/rmi/server/ActivationGroupImpl.java new file mode 100644 index 000000000..950ada2c2 --- /dev/null +++ b/src/share/classes/sun/rmi/server/ActivationGroupImpl.java @@ -0,0 +1,505 @@ +/* + * Copyright 1997-2006 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.server; + +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.net.ServerSocket; +import java.rmi.MarshalledObject; +import java.rmi.NoSuchObjectException; +import java.rmi.Remote; +import java.rmi.RemoteException; +import java.rmi.activation.Activatable; +import java.rmi.activation.ActivationDesc; +import java.rmi.activation.ActivationException; +import java.rmi.activation.ActivationGroup; +import java.rmi.activation.ActivationGroupID; +import java.rmi.activation.ActivationID; +import java.rmi.activation.UnknownObjectException; +import java.rmi.server.RMIClassLoader; +import java.rmi.server.RMIServerSocketFactory; +import java.rmi.server.RMISocketFactory; +import java.rmi.server.UnicastRemoteObject; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.List; +import sun.rmi.registry.RegistryImpl; + +/** + * The default activation group implementation. + * + * @author Ann Wollrath + * @since 1.2 + * @see java.rmi.activation.ActivationGroup + */ +public class ActivationGroupImpl extends ActivationGroup { + + // use serialVersionUID from JDK 1.2.2 for interoperability + private static final long serialVersionUID = 5758693559430427303L; + + /** maps persistent IDs to activated remote objects */ + private final Hashtable<ActivationID,ActiveEntry> active = + new Hashtable<ActivationID,ActiveEntry>(); + private boolean groupInactive = false; + private final ActivationGroupID groupID; + private final List<ActivationID> lockedIDs = new ArrayList<ActivationID>(); + + /** + * Creates a default activation group implementation. + * + * @param id the group's identifier + * @param data ignored + */ + public ActivationGroupImpl(ActivationGroupID id, MarshalledObject<?> data) + throws RemoteException + { + super(id); + groupID = id; + + /* + * Unexport activation group impl and attempt to export it on + * an unshared anonymous port. See 4692286. + */ + unexportObject(this, true); + RMIServerSocketFactory ssf = new ServerSocketFactoryImpl(); + UnicastRemoteObject.exportObject(this, 0, null, ssf); + + if (System.getSecurityManager() == null) { + try { + // Provide a default security manager. + System.setSecurityManager(new SecurityManager()); + + } catch (Exception e) { + throw new RemoteException("unable to set security manager", e); + } + } + } + + /** + * Trivial server socket factory used to export the activation group + * impl on an unshared port. + */ + private static class ServerSocketFactoryImpl + implements RMIServerSocketFactory + { + public ServerSocket createServerSocket(int port) throws IOException + { + RMISocketFactory sf = RMISocketFactory.getSocketFactory(); + if (sf == null) { + sf = RMISocketFactory.getDefaultSocketFactory(); + } + return sf.createServerSocket(port); + } + } + + /* + * Obtains a lock on the ActivationID id before returning. Allows only one + * thread at a time to hold a lock on a particular id. If the lock for id + * is in use, all requests for an equivalent (in the Object.equals sense) + * id will wait for the id to be notified and use the supplied id as the + * next lock. The caller of "acquireLock" must execute the "releaseLock" + * method" to release the lock and "notifyAll" waiters for the id lock + * obtained from this method. The typical usage pattern is as follows: + * + * try { + * acquireLock(id); + * // do stuff pertaining to id... + * } finally { + * releaseLock(id); + * checkInactiveGroup(); + * } + */ + private void acquireLock(ActivationID id) { + + ActivationID waitForID; + + for (;;) { + + synchronized (lockedIDs) { + int index = lockedIDs.indexOf(id); + if (index < 0) { + lockedIDs.add(id); + return; + } else { + waitForID = lockedIDs.get(index); + } + } + + synchronized (waitForID) { + synchronized (lockedIDs) { + int index = lockedIDs.indexOf(waitForID); + if (index < 0) continue; + ActivationID actualID = lockedIDs.get(index); + if (actualID != waitForID) + /* + * don't wait on an id that won't be notified. + */ + continue; + } + + try { + waitForID.wait(); + } catch (InterruptedException ignore) { + } + } + } + + } + + /* + * Releases the id lock obtained via the "acquireLock" method and then + * notifies all threads waiting on the lock. + */ + private void releaseLock(ActivationID id) { + synchronized (lockedIDs) { + id = lockedIDs.remove(lockedIDs.indexOf(id)); + } + + synchronized (id) { + id.notifyAll(); + } + } + + /** + * Creates a new instance of an activatable remote object. The + * <code>Activator</code> calls this method to create an activatable + * object in this group. This method should be idempotent; a call to + * activate an already active object should return the previously + * activated object. + * + * Note: this method assumes that the Activator will only invoke + * newInstance for the same object in a serial fashion (i.e., + * the activator will not allow the group to see concurrent requests + * to activate the same object. + * + * @param id the object's activation identifier + * @param desc the object's activation descriptor + * @return a marshalled object containing the activated object's stub + */ + public MarshalledObject<? extends Remote> + newInstance(final ActivationID id, + final ActivationDesc desc) + throws ActivationException, RemoteException + { + RegistryImpl.checkAccess("ActivationInstantiator.newInstance"); + + if (!groupID.equals(desc.getGroupID())) + throw new ActivationException("newInstance in wrong group"); + + try { + acquireLock(id); + synchronized (this) { + if (groupInactive == true) + throw new InactiveGroupException("group is inactive"); + } + + ActiveEntry entry = active.get(id); + if (entry != null) + return entry.mobj; + + String className = desc.getClassName(); + + final Class<? extends Remote> cl = + RMIClassLoader.loadClass(desc.getLocation(), className) + .asSubclass(Remote.class); + Remote impl = null; + + final Thread t = Thread.currentThread(); + final ClassLoader savedCcl = t.getContextClassLoader(); + ClassLoader objcl = cl.getClassLoader(); + final ClassLoader ccl = covers(objcl, savedCcl) ? objcl : savedCcl; + + /* + * Fix for 4164971: allow non-public activatable class + * and/or constructor, create the activatable object in a + * privileged block + */ + try { + /* + * The code below is in a doPrivileged block to + * protect against user code which code might have set + * a global socket factory (in which case application + * code would be on the stack). + */ + impl = AccessController.doPrivileged( + new PrivilegedExceptionAction<Remote>() { + public Remote run() throws InstantiationException, + NoSuchMethodException, IllegalAccessException, + InvocationTargetException + { + Constructor<? extends Remote> constructor = + cl.getDeclaredConstructor( + ActivationID.class, MarshalledObject.class); + constructor.setAccessible(true); + try { + /* + * Fix for 4289544: make sure to set the + * context class loader to be the class + * loader of the impl class before + * constructing that class. + */ + t.setContextClassLoader(ccl); + return constructor.newInstance(id, + desc.getData()); + } finally { + t.setContextClassLoader(savedCcl); + } + } + }); + } catch (PrivilegedActionException pae) { + Throwable e = pae.getException(); + + // narrow the exception's type and rethrow it + if (e instanceof InstantiationException) { + throw (InstantiationException) e; + } else if (e instanceof NoSuchMethodException) { + throw (NoSuchMethodException) e; + } else if (e instanceof IllegalAccessException) { + throw (IllegalAccessException) e; + } else if (e instanceof InvocationTargetException) { + throw (InvocationTargetException) e; + } else if (e instanceof RuntimeException) { + throw (RuntimeException) e; + } else if (e instanceof Error) { + throw (Error) e; + } + } + + entry = new ActiveEntry(impl); + active.put(id, entry); + return entry.mobj; + + } catch (NoSuchMethodException e) { + /* user forgot to provide activatable constructor? */ + throw new ActivationException + ("Activatable object must provide an activation"+ + " constructor", e); + + } catch (NoSuchMethodError e) { + /* code recompiled and user forgot to provide + * activatable constructor? + */ + throw new ActivationException + ("Activatable object must provide an activation"+ + " constructor", e ); + + } catch (InvocationTargetException e) { + throw new ActivationException("exception in object constructor", + e.getTargetException()); + + } catch (Exception e) { + throw new ActivationException("unable to activate object", e); + } finally { + releaseLock(id); + checkInactiveGroup(); + } + } + + + /** + * The group's <code>inactiveObject</code> method is called + * indirectly via a call to the <code>Activatable.inactive</code> + * method. A remote object implementation must call + * <code>Activatable</code>'s <code>inactive</code> method when + * that object deactivates (the object deems that it is no longer + * active). If the object does not call + * <code>Activatable.inactive</code> when it deactivates, the + * object will never be garbage collected since the group keeps + * strong references to the objects it creates. <p> + * + * The group's <code>inactiveObject</code> method + * unexports the remote object from the RMI runtime so that the + * object can no longer receive incoming RMI calls. This call will + * only succeed if the object has no pending/executing calls. If + * the object does have pending/executing RMI calls, then false + * will be returned. + * + * If the object has no pending/executing calls, the object is + * removed from the RMI runtime and the group informs its + * <code>ActivationMonitor</code> (via the monitor's + * <code>inactiveObject</code> method) that the remote object is + * not currently active so that the remote object will be + * re-activated by the activator upon a subsequent activation + * request. + * + * @param id the object's activation identifier + * @returns true if the operation succeeds (the operation will + * succeed if the object in currently known to be active and is + * either already unexported or is currently exported and has no + * pending/executing calls); false is returned if the object has + * pending/executing calls in which case it cannot be deactivated + * @exception UnknownObjectException if object is unknown (may already + * be inactive) + * @exception RemoteException if call informing monitor fails + */ + public boolean inactiveObject(ActivationID id) + throws ActivationException, UnknownObjectException, RemoteException + { + + try { + acquireLock(id); + synchronized (this) { + if (groupInactive == true) + throw new ActivationException("group is inactive"); + } + + ActiveEntry entry = active.get(id); + if (entry == null) { + // REMIND: should this be silent? + throw new UnknownObjectException("object not active"); + } + + try { + if (Activatable.unexportObject(entry.impl, false) == false) + return false; + } catch (NoSuchObjectException allowUnexportedObjects) { + } + + try { + super.inactiveObject(id); + } catch (UnknownObjectException allowUnregisteredObjects) { + } + + active.remove(id); + + } finally { + releaseLock(id); + checkInactiveGroup(); + } + + return true; + } + + /* + * Determines if the group has become inactive and + * marks it as such. + */ + private void checkInactiveGroup() { + boolean groupMarkedInactive = false; + synchronized (this) { + if (active.size() == 0 && lockedIDs.size() == 0 && + groupInactive == false) + { + groupInactive = true; + groupMarkedInactive = true; + } + } + + if (groupMarkedInactive) { + try { + super.inactiveGroup(); + } catch (Exception ignoreDeactivateFailure) { + } + + try { + UnicastRemoteObject.unexportObject(this, true); + } catch (NoSuchObjectException allowUnexportedGroup) { + } + } + } + + /** + * The group's <code>activeObject</code> method is called when an + * object is exported (either by <code>Activatable</code> object + * construction or an explicit call to + * <code>Activatable.exportObject</code>. The group must inform its + * <code>ActivationMonitor</code> that the object is active (via + * the monitor's <code>activeObject</code> method) if the group + * hasn't already done so. + * + * @param id the object's identifier + * @param obj the remote object implementation + * @exception UnknownObjectException if object is not registered + * @exception RemoteException if call informing monitor fails + */ + public void activeObject(ActivationID id, Remote impl) + throws ActivationException, UnknownObjectException, RemoteException + { + + try { + acquireLock(id); + synchronized (this) { + if (groupInactive == true) + throw new ActivationException("group is inactive"); + } + if (!active.contains(id)) { + ActiveEntry entry = new ActiveEntry(impl); + active.put(id, entry); + // created new entry, so inform monitor of active object + try { + super.activeObject(id, entry.mobj); + } catch (RemoteException e) { + // daemon can still find it by calling newInstance + } + } + } finally { + releaseLock(id); + checkInactiveGroup(); + } + } + + /** + * Entry in table for active object. + */ + private static class ActiveEntry { + Remote impl; + MarshalledObject<Remote> mobj; + + ActiveEntry(Remote impl) throws ActivationException { + this.impl = impl; + try { + this.mobj = new MarshalledObject<Remote>(impl); + } catch (IOException e) { + throw new + ActivationException("failed to marshal remote object", e); + } + } + } + + /** + * Returns true if the first argument is either equal to, or is a + * descendant of, the second argument. Null is treated as the root of + * the tree. + */ + private static boolean covers(ClassLoader sub, ClassLoader sup) { + if (sup == null) { + return true; + } else if (sub == null) { + return false; + } + do { + if (sub == sup) { + return true; + } + sub = sub.getParent(); + } while (sub != null); + return false; + } +} diff --git a/src/share/classes/sun/rmi/server/ActivationGroupInit.java b/src/share/classes/sun/rmi/server/ActivationGroupInit.java new file mode 100644 index 000000000..4ee82133c --- /dev/null +++ b/src/share/classes/sun/rmi/server/ActivationGroupInit.java @@ -0,0 +1,85 @@ +/* + * Copyright 1997-2002 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.server; + +import java.rmi.activation.ActivationGroupDesc; +import java.rmi.activation.ActivationGroupID; +import java.rmi.activation.ActivationGroup; + +/** + * This is the bootstrap code to start a VM executing an activation + * group. + * + * The activator spawns (as a child process) an activation group as needed + * and directs activation requests to the appropriate activation + * group. After spawning the VM, the activator passes some + * information to the bootstrap code via its stdin: <p> + * <ul> + * <li> the activation group's id, + * <li> the activation group's descriptor (an instance of the class + * java.rmi.activation.ActivationGroupDesc) for the group, adn + * <li> the group's incarnation number. + * </ul><p> + * + * When the bootstrap VM starts executing, it reads group id and + * descriptor from its stdin so that it can create the activation + * group for the VM. + * + * @author Ann Wollrath + */ +public abstract class ActivationGroupInit +{ + /** + * Main program to start a VM for an activation group. + */ + public static void main(String args[]) + { + try { + if (System.getSecurityManager() == null) { + System.setSecurityManager(new SecurityManager()); + } + // read group id, descriptor, and incarnation number from stdin + MarshalInputStream in = new MarshalInputStream(System.in); + ActivationGroupID id = (ActivationGroupID)in.readObject(); + ActivationGroupDesc desc = (ActivationGroupDesc)in.readObject(); + long incarnation = in.readLong(); + + // create and set group for the VM + ActivationGroup.createGroup(id, desc, incarnation); + } catch (Exception e) { + System.err.println("Exception in starting ActivationGroupInit:"); + e.printStackTrace(); + } finally { + try { + System.in.close(); + // note: system out/err shouldn't be closed + // since the parent may want to read them. + } catch (Exception ex) { + // ignore exceptions + } + } + } +} diff --git a/src/share/classes/sun/rmi/server/Dispatcher.java b/src/share/classes/sun/rmi/server/Dispatcher.java new file mode 100644 index 000000000..09d35fa3a --- /dev/null +++ b/src/share/classes/sun/rmi/server/Dispatcher.java @@ -0,0 +1,49 @@ +/* + * Copyright 1996 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ +package sun.rmi.server; + +import java.rmi.Remote; +import java.rmi.server.RemoteCall; + +/** + * The Dispatcher interface allows the transport to make + * the upcall to the server side remote reference. + */ +public interface Dispatcher { + + /** + * Call to dispatch to the remote object (on the server side). + * The up-call to the server and the marshaling of return result + * (or exception) should be handled before returning from this + * method. + * @param obj the target remote object for the call + * @param call the "remote call" from which operation and + * method arguments can be obtained. + * @exception RemoteException unable to marshal + * return result + */ + void dispatch(Remote obj, RemoteCall call) + throws java.io.IOException; +} diff --git a/src/share/classes/sun/rmi/server/InactiveGroupException.java b/src/share/classes/sun/rmi/server/InactiveGroupException.java new file mode 100644 index 000000000..1464ef540 --- /dev/null +++ b/src/share/classes/sun/rmi/server/InactiveGroupException.java @@ -0,0 +1,50 @@ +/* + * Copyright 2005 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.server; + +import java.rmi.activation.ActivationException; + +/** + * Thrown if a local or remote call is made on a group implementation + * instance that is inactive. + * + * @author Sun Microsystems, Inc. + * + * @since 1.6 + */ +public class InactiveGroupException extends ActivationException { + + private static final long serialVersionUID = -7491041778450214975L; + + /** + * Constructs an instance with the specified detail message. + * + * @param s the detail message + */ + public InactiveGroupException(String s) { + super(s); + } +} diff --git a/src/share/classes/sun/rmi/server/LoaderHandler.java b/src/share/classes/sun/rmi/server/LoaderHandler.java new file mode 100644 index 000000000..d4a9183e4 --- /dev/null +++ b/src/share/classes/sun/rmi/server/LoaderHandler.java @@ -0,0 +1,1198 @@ +/* + * Copyright 1996-2005 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.server; + +import java.io.File; +import java.io.FilePermission; +import java.io.IOException; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; +import java.lang.reflect.Modifier; +import java.lang.reflect.Proxy; +import java.net.JarURLConnection; +import java.net.MalformedURLException; +import java.net.SocketPermission; +import java.net.URL; +import java.net.URLClassLoader; +import java.net.URLConnection; +import java.security.AccessControlContext; +import java.security.CodeSource; +import java.security.Permission; +import java.security.Permissions; +import java.security.PermissionCollection; +import java.security.Policy; +import java.security.ProtectionDomain; +import java.rmi.server.LogStream; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.StringTokenizer; +import java.util.WeakHashMap; +import sun.rmi.runtime.Log; +import sun.security.action.GetPropertyAction; + +/** + * <code>LoaderHandler</code> provides the implementation of the static + * methods of the <code>java.rmi.server.RMIClassLoader</code> class. + * + * @author Ann Wollrath + * @author Peter Jones + * @author Laird Dornin + */ +public final class LoaderHandler { + + /** RMI class loader log level */ + static final int logLevel = LogStream.parseLevel( + (String) java.security.AccessController.doPrivileged( + new GetPropertyAction("sun.rmi.loader.logLevel"))); + + /* loader system log */ + static final Log loaderLog = + Log.getLog("sun.rmi.loader", "loader", LoaderHandler.logLevel); + + /** + * value of "java.rmi.server.codebase" property, as cached at class + * initialization time. It may contain malformed URLs. + */ + private static String codebaseProperty = null; + static { + String prop = (String) java.security.AccessController.doPrivileged( + new GetPropertyAction("java.rmi.server.codebase")); + if (prop != null && prop.trim().length() > 0) { + codebaseProperty = prop; + } + } + + /** list of URLs represented by the codebase property, if valid */ + private static URL[] codebaseURLs = null; + + /** table of class loaders that use codebase property for annotation */ + private static final Map codebaseLoaders = + Collections.synchronizedMap(new IdentityHashMap(5)); + static { + for (ClassLoader codebaseLoader = ClassLoader.getSystemClassLoader(); + codebaseLoader != null; + codebaseLoader = codebaseLoader.getParent()) + { + codebaseLoaders.put(codebaseLoader, null); + } + } + + /** + * table mapping codebase URL path and context class loader pairs + * to class loader instances. Entries hold class loaders with weak + * references, so this table does not prevent loaders from being + * garbage collected. + */ + private static final HashMap loaderTable = new HashMap(5); + + /** reference queue for cleared class loader entries */ + private static final ReferenceQueue refQueue = new ReferenceQueue(); + + /* + * Disallow anyone from creating one of these. + */ + private LoaderHandler() {} + + /** + * Returns an array of URLs initialized with the value of the + * java.rmi.server.codebase property as the URL path. + */ + private static synchronized URL[] getDefaultCodebaseURLs() + throws MalformedURLException + { + /* + * If it hasn't already been done, convert the codebase property + * into an array of URLs; this may throw a MalformedURLException. + */ + if (codebaseURLs == null) { + if (codebaseProperty != null) { + codebaseURLs = pathToURLs(codebaseProperty); + } else { + codebaseURLs = new URL[0]; + } + } + return codebaseURLs; + } + + /** + * Load a class from a network location (one or more URLs), + * but first try to resolve the named class through the given + * "default loader". + */ + public static Class loadClass(String codebase, String name, + ClassLoader defaultLoader) + throws MalformedURLException, ClassNotFoundException + { + if (loaderLog.isLoggable(Log.BRIEF)) { + loaderLog.log(Log.BRIEF, + "name = \"" + name + "\", " + + "codebase = \"" + (codebase != null ? codebase : "") + "\"" + + (defaultLoader != null ? + ", defaultLoader = " + defaultLoader : "")); + } + + URL[] urls; + if (codebase != null) { + urls = pathToURLs(codebase); + } else { + urls = getDefaultCodebaseURLs(); + } + + if (defaultLoader != null) { + try { + Class c = Class.forName(name, false, defaultLoader); + if (loaderLog.isLoggable(Log.VERBOSE)) { + loaderLog.log(Log.VERBOSE, + "class \"" + name + "\" found via defaultLoader, " + + "defined by " + c.getClassLoader()); + } + return c; + } catch (ClassNotFoundException e) { + } + } + + return loadClass(urls, name); + } + + /** + * Returns the class annotation (representing the location for + * a class) that RMI will use to annotate the call stream when + * marshalling objects of the given class. + */ + public static String getClassAnnotation(Class cl) { + String name = cl.getName(); + + /* + * Class objects for arrays of primitive types never need an + * annotation, because they never need to be (or can be) downloaded. + * + * REMIND: should we (not) be annotating classes that are in + * "java.*" packages? + */ + int nameLength = name.length(); + if (nameLength > 0 && name.charAt(0) == '[') { + // skip past all '[' characters (see bugid 4211906) + int i = 1; + while (nameLength > i && name.charAt(i) == '[') { + i++; + } + if (nameLength > i && name.charAt(i) != 'L') { + return null; + } + } + + /* + * Get the class's class loader. If it is null, the system class + * loader, an ancestor of the base class loader (such as the loader + * for installed extensions), return the value of the + * "java.rmi.server.codebase" property. + */ + ClassLoader loader = cl.getClassLoader(); + if (loader == null || codebaseLoaders.containsKey(loader)) { + return codebaseProperty; + } + + /* + * Get the codebase URL path for the class loader, if it supports + * such a notion (i.e., if it is a URLClassLoader or subclass). + */ + String annotation = null; + if (loader instanceof Loader) { + /* + * If the class loader is one of our RMI class loaders, we have + * already computed the class annotation string, and no + * permissions are required to know the URLs. + */ + annotation = ((Loader) loader).getClassAnnotation(); + + } else if (loader instanceof URLClassLoader) { + try { + URL[] urls = ((URLClassLoader) loader).getURLs(); + if (urls != null) { + /* + * If the class loader is not one of our RMI class loaders, + * we must verify that the current access control context + * has permission to know all of these URLs. + */ + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + Permissions perms = new Permissions(); + for (int i = 0; i < urls.length; i++) { + Permission p = + urls[i].openConnection().getPermission(); + if (p != null) { + if (!perms.implies(p)) { + sm.checkPermission(p); + perms.add(p); + } + } + } + } + + annotation = urlsToPath(urls); + } + } catch (SecurityException e) { + /* + * If access was denied to the knowledge of the class + * loader's URLs, fall back to the default behavior. + */ + } catch (IOException e) { + /* + * This shouldn't happen, although it is declared to be + * thrown by openConnection() and getPermission(). If it + * does happen, forget about this class loader's URLs and + * fall back to the default behavior. + */ + } + } + + if (annotation != null) { + return annotation; + } else { + return codebaseProperty; // REMIND: does this make sense?? + } + } + + /** + * Returns a classloader that loads classes from the given codebase URL + * path. The parent classloader of the returned classloader is the + * context class loader. + */ + public static ClassLoader getClassLoader(String codebase) + throws MalformedURLException + { + ClassLoader parent = getRMIContextClassLoader(); + + URL[] urls; + if (codebase != null) { + urls = pathToURLs(codebase); + } else { + urls = getDefaultCodebaseURLs(); + } + + /* + * If there is a security manager, the current access control + * context must have the "getClassLoader" RuntimePermission. + */ + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(new RuntimePermission("getClassLoader")); + } else { + /* + * But if no security manager is set, disable access to + * RMI class loaders and simply return the parent loader. + */ + return parent; + } + + Loader loader = lookupLoader(urls, parent); + + /* + * Verify that the caller has permission to access this loader. + */ + if (loader != null) { + loader.checkPermissions(); + } + + return loader; + } + + /** + * Return the security context of the given class loader. + */ + public static Object getSecurityContext(ClassLoader loader) { + /* + * REMIND: This is a bogus JDK1.1-compatible implementation. + * This method should never be called by application code anyway + * (hence the deprecation), but should it do something different + * and perhaps more useful, like return a String or a URL[]? + */ + if (loader instanceof Loader) { + URL[] urls = ((Loader) loader).getURLs(); + if (urls.length > 0) { + return urls[0]; + } + } + return null; + } + + /** + * Register a class loader as one whose classes should always be + * annotated with the value of the "java.rmi.server.codebase" property. + */ + public static void registerCodebaseLoader(ClassLoader loader) { + codebaseLoaders.put(loader, null); + } + + /** + * Load a class from the RMI class loader corresponding to the given + * codebase URL path in the current execution context. + */ + private static Class loadClass(URL[] urls, String name) + throws ClassNotFoundException + { + ClassLoader parent = getRMIContextClassLoader(); + if (loaderLog.isLoggable(Log.VERBOSE)) { + loaderLog.log(Log.VERBOSE, + "(thread context class loader: " + parent + ")"); + } + + /* + * If no security manager is set, disable access to RMI class + * loaders and simply delegate request to the parent loader + * (see bugid 4140511). + */ + SecurityManager sm = System.getSecurityManager(); + if (sm == null) { + try { + Class c = Class.forName(name, false, parent); + if (loaderLog.isLoggable(Log.VERBOSE)) { + loaderLog.log(Log.VERBOSE, + "class \"" + name + "\" found via " + + "thread context class loader " + + "(no security manager: codebase disabled), " + + "defined by " + c.getClassLoader()); + } + return c; + } catch (ClassNotFoundException e) { + if (loaderLog.isLoggable(Log.BRIEF)) { + loaderLog.log(Log.BRIEF, + "class \"" + name + "\" not found via " + + "thread context class loader " + + "(no security manager: codebase disabled)", e); + } + throw new ClassNotFoundException(e.getMessage() + + " (no security manager: RMI class loader disabled)", + e.getException()); + } + } + + /* + * Get or create the RMI class loader for this codebase URL path + * and parent class loader pair. + */ + Loader loader = lookupLoader(urls, parent); + + try { + if (loader != null) { + /* + * Verify that the caller has permission to access this loader. + */ + loader.checkPermissions(); + } + } catch (SecurityException e) { + /* + * If the current access control context does not have permission + * to access all of the URLs in the codebase path, wrap the + * resulting security exception in a ClassNotFoundException, so + * the caller can handle this outcome just like any other class + * loading failure (see bugid 4146529). + */ + try { + /* + * But first, check to see if the named class could have been + * resolved without the security-offending codebase anyway; + * if so, return successfully (see bugids 4191926 & 4349670). + */ + Class c = Class.forName(name, false, parent); + if (loaderLog.isLoggable(Log.VERBOSE)) { + loaderLog.log(Log.VERBOSE, + "class \"" + name + "\" found via " + + "thread context class loader " + + "(access to codebase denied), " + + "defined by " + c.getClassLoader()); + } + return c; + } catch (ClassNotFoundException unimportant) { + /* + * Presumably the security exception is the more important + * exception to report in this case. + */ + if (loaderLog.isLoggable(Log.BRIEF)) { + loaderLog.log(Log.BRIEF, + "class \"" + name + "\" not found via " + + "thread context class loader " + + "(access to codebase denied)", e); + } + throw new ClassNotFoundException( + "access to class loader denied", e); + } + } + + try { + Class c = Class.forName(name, false, loader); + if (loaderLog.isLoggable(Log.VERBOSE)) { + loaderLog.log(Log.VERBOSE, + "class \"" + name + "\" " + "found via codebase, " + + "defined by " + c.getClassLoader()); + } + return c; + } catch (ClassNotFoundException e) { + if (loaderLog.isLoggable(Log.BRIEF)) { + loaderLog.log(Log.BRIEF, + "class \"" + name + "\" not found via codebase", e); + } + throw e; + } + } + + /** + * Define and return a dynamic proxy class in a class loader with + * URLs supplied in the given location. The proxy class will + * implement interface classes named by the given array of + * interface names. + */ + public static Class loadProxyClass(String codebase, String[] interfaces, + ClassLoader defaultLoader) + throws MalformedURLException, ClassNotFoundException + { + if (loaderLog.isLoggable(Log.BRIEF)) { + loaderLog.log(Log.BRIEF, + "interfaces = " + Arrays.asList(interfaces) + ", " + + "codebase = \"" + (codebase != null ? codebase : "") + "\"" + + (defaultLoader != null ? + ", defaultLoader = " + defaultLoader : "")); + } + + /* + * This method uses a fairly complex algorithm to load the + * proxy class and its interface classes in order to maximize + * the likelihood that the proxy's codebase annotation will be + * preserved. The algorithm is (assuming that all of the + * proxy interface classes are public): + * + * If the default loader is not null, try to load the proxy + * interfaces through that loader. If the interfaces can be + * loaded in that loader, try to define the proxy class in an + * RMI class loader (child of the context class loader) before + * trying to define the proxy in the default loader. If the + * attempt to define the proxy class succeeds, the codebase + * annotation is preserved. If the attempt fails, try to + * define the proxy class in the default loader. + * + * If the interface classes can not be loaded from the default + * loader or the default loader is null, try to load them from + * the RMI class loader. Then try to define the proxy class + * in the RMI class loader. + * + * Additionally, if any of the proxy interface classes are not + * public, all of the non-public interfaces must reside in the + * same class loader or it will be impossible to define the + * proxy class (an IllegalAccessError will be thrown). An + * attempt to load the interfaces from the default loader is + * made. If the attempt fails, a second attempt will be made + * to load the interfaces from the RMI loader. If all of the + * non-public interfaces classes do reside in the same class + * loader, then we attempt to define the proxy class in the + * class loader of the non-public interfaces. No other + * attempt to define the proxy class will be made. + */ + ClassLoader parent = getRMIContextClassLoader(); + if (loaderLog.isLoggable(Log.VERBOSE)) { + loaderLog.log(Log.VERBOSE, + "(thread context class loader: " + parent + ")"); + } + + URL[] urls; + if (codebase != null) { + urls = pathToURLs(codebase); + } else { + urls = getDefaultCodebaseURLs(); + } + + /* + * If no security manager is set, disable access to RMI class + * loaders and use the would-de parent instead. + */ + SecurityManager sm = System.getSecurityManager(); + if (sm == null) { + try { + Class c = loadProxyClass(interfaces, defaultLoader, parent, + false); + if (loaderLog.isLoggable(Log.VERBOSE)) { + loaderLog.log(Log.VERBOSE, + "(no security manager: codebase disabled) " + + "proxy class defined by " + c.getClassLoader()); + } + return c; + } catch (ClassNotFoundException e) { + if (loaderLog.isLoggable(Log.BRIEF)) { + loaderLog.log(Log.BRIEF, + "(no security manager: codebase disabled) " + + "proxy class resolution failed", e); + } + throw new ClassNotFoundException(e.getMessage() + + " (no security manager: RMI class loader disabled)", + e.getException()); + } + } + + /* + * Get or create the RMI class loader for this codebase URL path + * and parent class loader pair. + */ + Loader loader = lookupLoader(urls, parent); + + try { + if (loader != null) { + /* + * Verify that the caller has permission to access this loader. + */ + loader.checkPermissions(); + } + } catch (SecurityException e) { + /* + * If the current access control context does not have permission + * to access all of the URLs in the codebase path, wrap the + * resulting security exception in a ClassNotFoundException, so + * the caller can handle this outcome just like any other class + * loading failure (see bugid 4146529). + */ + try { + /* + * But first, check to see if the proxy class could have been + * resolved without the security-offending codebase anyway; + * if so, return successfully (see bugids 4191926 & 4349670). + */ + Class c = loadProxyClass(interfaces, defaultLoader, parent, + false); + if (loaderLog.isLoggable(Log.VERBOSE)) { + loaderLog.log(Log.VERBOSE, + "(access to codebase denied) " + + "proxy class defined by " + c.getClassLoader()); + } + return c; + } catch (ClassNotFoundException unimportant) { + /* + * Presumably the security exception is the more important + * exception to report in this case. + */ + if (loaderLog.isLoggable(Log.BRIEF)) { + loaderLog.log(Log.BRIEF, + "(access to codebase denied) " + + "proxy class resolution failed", e); + } + throw new ClassNotFoundException( + "access to class loader denied", e); + } + } + + try { + Class c = loadProxyClass(interfaces, defaultLoader, loader, true); + if (loaderLog.isLoggable(Log.VERBOSE)) { + loaderLog.log(Log.VERBOSE, + "proxy class defined by " + c.getClassLoader()); + } + return c; + } catch (ClassNotFoundException e) { + if (loaderLog.isLoggable(Log.BRIEF)) { + loaderLog.log(Log.BRIEF, + "proxy class resolution failed", e); + } + throw e; + } + } + + /** + * Define a proxy class in the default loader if appropriate. + * Define the class in an RMI class loader otherwise. The proxy + * class will implement classes which are named in the supplied + * interfaceNames. + */ + private static Class loadProxyClass(String[] interfaceNames, + ClassLoader defaultLoader, + ClassLoader codebaseLoader, + boolean preferCodebase) + throws ClassNotFoundException + { + ClassLoader proxyLoader = null; + Class[] classObjs = new Class[interfaceNames.length]; + boolean[] nonpublic = { false }; + + defaultLoaderCase: + if (defaultLoader != null) { + try { + proxyLoader = + loadProxyInterfaces(interfaceNames, defaultLoader, + classObjs, nonpublic); + if (loaderLog.isLoggable(Log.VERBOSE)) { + ClassLoader[] definingLoaders = + new ClassLoader[classObjs.length]; + for (int i = 0; i < definingLoaders.length; i++) { + definingLoaders[i] = classObjs[i].getClassLoader(); + } + loaderLog.log(Log.VERBOSE, + "proxy interfaces found via defaultLoader, " + + "defined by " + Arrays.asList(definingLoaders)); + } + } catch (ClassNotFoundException e) { + break defaultLoaderCase; + } + if (!nonpublic[0]) { + if (preferCodebase) { + try { + return Proxy.getProxyClass(codebaseLoader, classObjs); + } catch (IllegalArgumentException e) { + } + } + proxyLoader = defaultLoader; + } + return loadProxyClass(proxyLoader, classObjs); + } + + nonpublic[0] = false; + proxyLoader = loadProxyInterfaces(interfaceNames, codebaseLoader, + classObjs, nonpublic); + if (loaderLog.isLoggable(Log.VERBOSE)) { + ClassLoader[] definingLoaders = new ClassLoader[classObjs.length]; + for (int i = 0; i < definingLoaders.length; i++) { + definingLoaders[i] = classObjs[i].getClassLoader(); + } + loaderLog.log(Log.VERBOSE, + "proxy interfaces found via codebase, " + + "defined by " + Arrays.asList(definingLoaders)); + } + if (!nonpublic[0]) { + proxyLoader = codebaseLoader; + } + return loadProxyClass(proxyLoader, classObjs); + } + + /** + * Define a proxy class in the given class loader. The proxy + * class will implement the given interfaces Classes. + */ + private static Class loadProxyClass(ClassLoader loader, Class[] interfaces) + throws ClassNotFoundException + { + try { + return Proxy.getProxyClass(loader, interfaces); + } catch (IllegalArgumentException e) { + throw new ClassNotFoundException( + "error creating dynamic proxy class", e); + } + } + + /* + * Load Class objects for the names in the interfaces array fron + * the given class loader. + * + * We pass classObjs and nonpublic arrays to avoid needing a + * multi-element return value. nonpublic is an array to enable + * the method to take a boolean argument by reference. + * + * nonpublic array is needed to signal when the return value of + * this method should be used as the proxy class loader. Because + * null represents a valid class loader, that value is + * insufficient to signal that the return value should not be used + * as the proxy class loader. + */ + private static ClassLoader loadProxyInterfaces(String[] interfaces, + ClassLoader loader, + Class[] classObjs, + boolean[] nonpublic) + throws ClassNotFoundException + { + /* loader of a non-public interface class */ + ClassLoader nonpublicLoader = null; + + for (int i = 0; i < interfaces.length; i++) { + Class cl = + (classObjs[i] = Class.forName(interfaces[i], false, loader)); + + if (!Modifier.isPublic(cl.getModifiers())) { + ClassLoader current = cl.getClassLoader(); + if (loaderLog.isLoggable(Log.VERBOSE)) { + loaderLog.log(Log.VERBOSE, + "non-public interface \"" + interfaces[i] + + "\" defined by " + current); + } + if (!nonpublic[0]) { + nonpublicLoader = current; + nonpublic[0] = true; + } else if (current != nonpublicLoader) { + throw new IllegalAccessError( + "non-public interfaces defined in different " + + "class loaders"); + } + } + } + return nonpublicLoader; + } + + /** + * Convert a string containing a space-separated list of URLs into a + * corresponding array of URL objects, throwing a MalformedURLException + * if any of the URLs are invalid. + */ + private static URL[] pathToURLs(String path) + throws MalformedURLException + { + synchronized (pathToURLsCache) { + Object[] v = (Object[]) pathToURLsCache.get(path); + if (v != null) { + return ((URL[])v[0]); + } + } + StringTokenizer st = new StringTokenizer(path); // divide by spaces + URL[] urls = new URL[st.countTokens()]; + for (int i = 0; st.hasMoreTokens(); i++) { + urls[i] = new URL(st.nextToken()); + } + synchronized (pathToURLsCache) { + pathToURLsCache.put(path, + new Object[] {urls, new SoftReference(path)}); + } + return urls; + } + + /** map from weak(key=string) to [URL[], soft(key)] */ + private static final Map pathToURLsCache = new WeakHashMap(5); + + /** + * Convert an array of URL objects into a corresponding string + * containing a space-separated list of URLs. + * + * Note that if the array has zero elements, the return value is + * null, not the empty string. + */ + private static String urlsToPath(URL[] urls) { + if (urls.length == 0) { + return null; + } else if (urls.length == 1) { + return urls[0].toExternalForm(); + } else { + StringBuffer path = new StringBuffer(urls[0].toExternalForm()); + for (int i = 1; i < urls.length; i++) { + path.append(' '); + path.append(urls[i].toExternalForm()); + } + return path.toString(); + } + } + + /** + * Return the class loader to be used as the parent for an RMI class + * loader used in the current execution context. + */ + private static ClassLoader getRMIContextClassLoader() { + /* + * The current implementation simply uses the current thread's + * context class loader. + */ + return Thread.currentThread().getContextClassLoader(); + } + + /** + * Look up the RMI class loader for the given codebase URL path + * and the given parent class loader. A new class loader instance + * will be created and returned if no match is found. + */ + private static Loader lookupLoader(final URL[] urls, + final ClassLoader parent) + { + /* + * If the requested codebase URL path is empty, the supplied + * parent class loader will be sufficient. + * + * REMIND: To be conservative, this optimization is commented out + * for now so that it does not open a security hole in the future + * by providing untrusted code with direct access to the public + * loadClass() method of a class loader instance that it cannot + * get a reference to. (It's an unlikely optimization anyway.) + * + * if (urls.length == 0) { + * return parent; + * } + */ + + LoaderEntry entry; + Loader loader; + + synchronized (LoaderHandler.class) { + /* + * Take this opportunity to remove from the table entries + * whose weak references have been cleared. + */ + while ((entry = (LoaderEntry) refQueue.poll()) != null) { + if (!entry.removed) { // ignore entries removed below + loaderTable.remove(entry.key); + } + } + + /* + * Look up the codebase URL path and parent class loader pair + * in the table of RMI class loaders. + */ + LoaderKey key = new LoaderKey(urls, parent); + entry = (LoaderEntry) loaderTable.get(key); + + if (entry == null || (loader = (Loader) entry.get()) == null) { + /* + * If entry was in table but it's weak reference was cleared, + * remove it from the table and mark it as explicitly cleared, + * so that new matching entry that we put in the table will + * not be erroneously removed when this entry is processed + * from the weak reference queue. + */ + if (entry != null) { + loaderTable.remove(key); + entry.removed = true; + } + + /* + * A matching loader was not found, so create a new class + * loader instance for the requested codebase URL path and + * parent class loader. The instance is created within an + * access control context retricted to the permissions + * necessary to load classes from its codebase URL path. + */ + AccessControlContext acc = getLoaderAccessControlContext(urls); + loader = (Loader) java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + return new Loader(urls, parent); + } + }, acc); + + /* + * Finally, create an entry to hold the new loader with a + * weak reference and store it in the table with the key. + */ + entry = new LoaderEntry(key, loader); + loaderTable.put(key, entry); + } + } + + return loader; + } + + /** + * LoaderKey holds a codebase URL path and parent class loader pair + * used to look up RMI class loader instances in its class loader cache. + */ + private static class LoaderKey { + + private URL[] urls; + + private ClassLoader parent; + + private int hashValue; + + public LoaderKey(URL[] urls, ClassLoader parent) { + this.urls = urls; + this.parent = parent; + + if (parent != null) { + hashValue = parent.hashCode(); + } + for (int i = 0; i < urls.length; i++) { + hashValue ^= urls[i].hashCode(); + } + } + + public int hashCode() { + return hashValue; + } + + public boolean equals(Object obj) { + if (obj instanceof LoaderKey) { + LoaderKey other = (LoaderKey) obj; + if (parent != other.parent) { + return false; + } + if (urls == other.urls) { + return true; + } + if (urls.length != other.urls.length) { + return false; + } + for (int i = 0; i < urls.length; i++) { + if (!urls[i].equals(other.urls[i])) { + return false; + } + } + return true; + } else { + return false; + } + } + } + + /** + * LoaderEntry contains a weak reference to an RMIClassLoader. The + * weak reference is registered with the private static "refQueue" + * queue. The entry contains the codebase URL path and parent class + * loader key for the loader so that the mapping can be removed from + * the table efficiently when the weak reference is cleared. + */ + private static class LoaderEntry extends WeakReference { + + public LoaderKey key; + + /** + * set to true if the entry has been removed from the table + * because it has been replaced, so it should not be attempted + * to be removed again + */ + public boolean removed = false; + + public LoaderEntry(LoaderKey key, Loader loader) { + super(loader, refQueue); + this.key = key; + } + } + + /** + * Return the access control context that a loader for the given + * codebase URL path should execute with. + */ + private static AccessControlContext getLoaderAccessControlContext( + URL[] urls) + { + /* + * The approach used here is taken from the similar method + * getAccessControlContext() in the sun.applet.AppletPanel class. + */ + // begin with permissions granted to all code in current policy + PermissionCollection perms = (PermissionCollection) + java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + CodeSource codesource = new CodeSource(null, + (java.security.cert.Certificate[]) null); + Policy p = java.security.Policy.getPolicy(); + if (p != null) { + return p.getPermissions(codesource); + } else { + return new Permissions(); + } + } + }); + + // createClassLoader permission needed to create loader in context + perms.add(new RuntimePermission("createClassLoader")); + + // add permissions to read any "java.*" property + perms.add(new java.util.PropertyPermission("java.*","read")); + + // add permissions reuiqred to load from codebase URL path + addPermissionsForURLs(urls, perms, true); + + /* + * Create an AccessControlContext that consists of a single + * protection domain with only the permissions calculated above. + */ + ProtectionDomain pd = new ProtectionDomain( + new CodeSource((urls.length > 0 ? urls[0] : null), + (java.security.cert.Certificate[]) null), + perms); + return new AccessControlContext(new ProtectionDomain[] { pd }); + } + + /** + * Adds to the specified permission collection the permissions + * necessary to load classes from a loader with the specified URL + * path; if "forLoader" is true, also adds URL-specific + * permissions necessary for the security context that such a + * loader operates within, such as permissions necessary for + * granting automatic permissions to classes defined by the + * loader. A given permission is only added to the collection if + * it is not already implied by the collection. + */ + private static void addPermissionsForURLs(URL[] urls, + PermissionCollection perms, + boolean forLoader) + { + for (int i = 0; i < urls.length; i++) { + URL url = urls[i]; + try { + URLConnection urlConnection = url.openConnection(); + Permission p = urlConnection.getPermission(); + if (p != null) { + if (p instanceof FilePermission) { + /* + * If the codebase is a file, the permission required + * to actually read classes from the codebase URL is + * the permission to read all files beneath the last + * directory in the file path, either because JAR + * files can refer to other JAR files in the same + * directory, or because permission to read a + * directory is not implied by permission to read the + * contents of a directory, which all that might be + * granted. + */ + String path = p.getName(); + int endIndex = path.lastIndexOf(File.separatorChar); + if (endIndex != -1) { + path = path.substring(0, endIndex+1); + if (path.endsWith(File.separator)) { + path += "-"; + } + Permission p2 = new FilePermission(path, "read"); + if (!perms.implies(p2)) { + perms.add(p2); + } + perms.add(new FilePermission(path, "read")); + } else { + /* + * No directory separator: use permission to + * read the file. + */ + if (!perms.implies(p)) { + perms.add(p); + } + } + } else { + if (!perms.implies(p)) { + perms.add(p); + } + + /* + * If the purpose of these permissions is to grant + * them to an instance of a URLClassLoader subclass, + * we must add permission to connect to and accept + * from the host of non-"file:" URLs, otherwise the + * getPermissions() method of URLClassLoader will + * throw a security exception. + */ + if (forLoader) { + // get URL with meaningful host component + URL hostURL = url; + for (URLConnection conn = urlConnection; + conn instanceof JarURLConnection;) + { + hostURL = + ((JarURLConnection) conn).getJarFileURL(); + conn = hostURL.openConnection(); + } + String host = hostURL.getHost(); + if (host != null && + p.implies(new SocketPermission(host, + "resolve"))) + { + Permission p2 = + new SocketPermission(host, + "connect,accept"); + if (!perms.implies(p2)) { + perms.add(p2); + } + } + } + } + } + } catch (IOException e) { + /* + * This shouldn't happen, although it is declared to be + * thrown by openConnection() and getPermission(). If it + * does, don't bother granting or requiring any permissions + * for this URL. + */ + } + } + } + + /** + * Loader is the actual class of the RMI class loaders created + * by the RMIClassLoader static methods. + */ + private static class Loader extends URLClassLoader { + + /** parent class loader, kept here as an optimization */ + private ClassLoader parent; + + /** string form of loader's codebase URL path, also an optimization */ + private String annotation; + + /** permissions required to access loader through public API */ + private Permissions permissions; + + private Loader(URL[] urls, ClassLoader parent) { + super(urls, parent); + this.parent = parent; + + /* + * Precompute the permissions required to access the loader. + */ + permissions = new Permissions(); + addPermissionsForURLs(urls, permissions, false); + + /* + * Caching the value of class annotation string here assumes + * that the protected method addURL() is never called on this + * class loader. + */ + annotation = urlsToPath(urls); + } + + /** + * Return the string to be annotated with all classes loaded from + * this class loader. + */ + public String getClassAnnotation() { + return annotation; + } + + /** + * Check that the current access control context has all of the + * permissions necessary to load classes from this loader. + */ + private void checkPermissions() { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { // should never be null? + Enumeration enum_ = permissions.elements(); + while (enum_.hasMoreElements()) { + sm.checkPermission((Permission) enum_.nextElement()); + } + } + } + + /** + * Return the permissions to be granted to code loaded from the + * given code source. + */ + protected PermissionCollection getPermissions(CodeSource codesource) { + PermissionCollection perms = super.getPermissions(codesource); + /* + * Grant the same permissions that URLClassLoader would grant. + */ + return perms; + } + + /** + * Return a string representation of this loader (useful for + * debugging). + */ + public String toString() { + return super.toString() + "[\"" + annotation + "\"]"; + } + } +} diff --git a/src/share/classes/sun/rmi/server/MarshalInputStream.java b/src/share/classes/sun/rmi/server/MarshalInputStream.java new file mode 100644 index 000000000..a75b8051a --- /dev/null +++ b/src/share/classes/sun/rmi/server/MarshalInputStream.java @@ -0,0 +1,320 @@ +/* + * Copyright 1996-2003 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.server; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectStreamClass; +import java.io.StreamCorruptedException; +import java.net.URL; +import java.util.*; +import java.security.AccessControlException; +import java.security.Permission; + +import java.rmi.server.RMIClassLoader; + +/** + * MarshalInputStream is an extension of ObjectInputStream. When resolving + * a class, it reads an object from the stream written by a corresponding + * MarshalOutputStream. If the class to be resolved is not available + * locally, from the first class loader on the execution stack, or from the + * context class loader of the current thread, it will attempt to load the + * class from the location annotated by the sending MarshalOutputStream. + * This location object must be a string representing a path of URLs. + * + * A new MarshalInputStream should be created to deserialize remote objects or + * graphs containing remote objects. Objects are created from the stream + * using the ObjectInputStream.readObject method. + * + * @author Peter Jones + */ +public class MarshalInputStream extends ObjectInputStream { + + /** + * value of "java.rmi.server.useCodebaseOnly" property, + * as cached at class initialization time. + */ + private static final boolean useCodebaseOnlyProperty = + ((Boolean) java.security.AccessController.doPrivileged( + new sun.security.action.GetBooleanAction( + "java.rmi.server.useCodebaseOnly"))).booleanValue(); + + /** table to hold sun classes to which access is explicitly permitted */ + protected static Map permittedSunClasses = new HashMap(3); + + /** if true, don't try superclass first in resolveClass() */ + private boolean skipDefaultResolveClass = false; + + /** callbacks to make when done() called: maps Object to Runnable */ + private final Map doneCallbacks = new HashMap(3); + + /** + * if true, load classes (if not available locally) only from the + * URL specified by the "java.rmi.server.codebase" property. + */ + private boolean useCodebaseOnly = useCodebaseOnlyProperty; + + /* + * Fix for 4179055: The remote object services inside the + * activation daemon use stubs that are in the package + * sun.rmi.server. Classes for these stubs should be loaded from + * the classpath by RMI system code and not by the normal + * unmarshalling process as applications should not need to have + * permission to access the sun implementation classes. + * + * Note: this fix should be redone when API changes may be + * integrated + * + * During parameter unmarshalling RMI needs to explicitly permit + * access to three sun.* stub classes + */ + static { + try { + String system = + "sun.rmi.server.Activation$ActivationSystemImpl_Stub"; + String registry = "sun.rmi.registry.RegistryImpl_Stub"; + + permittedSunClasses.put(system, Class.forName(system)); + permittedSunClasses.put(registry, Class.forName(registry)); + + } catch (ClassNotFoundException e) { + throw new NoClassDefFoundError("Missing system class: " + + e.getMessage()); + } + } + + /** + * Load the "rmi" native library. + */ + static { + java.security.AccessController.doPrivileged( + new sun.security.action.LoadLibraryAction("rmi")); + } + + /** + * Create a new MarshalInputStream object. + */ + public MarshalInputStream(InputStream in) + throws IOException, StreamCorruptedException + { + super(in); + } + + /** + * Returns a callback previously registered via the setDoneCallback + * method with given key, or null if no callback has yet been registered + * with that key. + */ + public Runnable getDoneCallback(Object key) { + return (Runnable) doneCallbacks.get(key); // not thread-safe + } + + /** + * Registers a callback to make when this stream's done() method is + * invoked, along with a key for retrieving the same callback instance + * subsequently from the getDoneCallback method. + */ + public void setDoneCallback(Object key, Runnable callback) { + //assert(!doneCallbacks.contains(key)); + doneCallbacks.put(key, callback); // not thread-safe + } + + /** + * Indicates that the user of this MarshalInputStream is done reading + * objects from it, so all callbacks registered with the setDoneCallback + * method should now be (synchronously) executed. When this method + * returns, there are no more callbacks registered. + * + * This method is implicitly invoked by close() before it delegates to + * the superclass's close method. + */ + public void done() { + Iterator iter = doneCallbacks.values().iterator(); + while (iter.hasNext()) { // not thread-safe + Runnable callback = (Runnable) iter.next(); + callback.run(); + } + doneCallbacks.clear(); + } + + /** + * Closes this stream, implicitly invoking done() first. + */ + public void close() throws IOException { + done(); + super.close(); + } + + /** + * resolveClass is extended to acquire (if present) the location + * from which to load the specified class. + * It will find, load, and return the class. + */ + protected Class resolveClass(ObjectStreamClass classDesc) + throws IOException, ClassNotFoundException + { + /* + * Always read annotation written by MarshalOutputStream + * describing where to load class from. + */ + Object annotation = readLocation(); + + String className = classDesc.getName(); + + /* + * Unless we were told to skip this consideration, choose the + * "default loader" to simulate the default ObjectInputStream + * resolveClass mechanism (that is, choose the first non-null + * loader on the execution stack) to maximize the likelihood of + * type compatibility with calling code. (This consideration + * is skipped during server parameter unmarshalling using the 1.2 + * stub protocol, because there would never be a non-null class + * loader on the stack in that situation anyway.) + */ + ClassLoader defaultLoader = + skipDefaultResolveClass ? null : latestUserDefinedLoader(); + + /* + * If the "java.rmi.server.useCodebaseOnly" property was true or + * useCodebaseOnly() was called or the annotation is not a String, + * load from the local loader using the "java.rmi.server.codebase" + * URL. Otherwise, load from a loader using the codebase URL in + * the annotation. + */ + String codebase = null; + if (!useCodebaseOnly && annotation instanceof String) { + codebase = (String) annotation; + } + + try { + return RMIClassLoader.loadClass(codebase, className, + defaultLoader); + } catch (AccessControlException e) { + return checkSunClass(className, e); + } catch (ClassNotFoundException e) { + /* + * Fix for 4442373: delegate to ObjectInputStream.resolveClass() + * to resolve primitive classes. + */ + try { + if (Character.isLowerCase(className.charAt(0)) && + className.indexOf('.') == -1) + { + return super.resolveClass(classDesc); + } + } catch (ClassNotFoundException e2) { + } + throw e; + } + } + + /** + * resolveProxyClass is extended to acquire (if present) the location + * to determine the class loader to define the proxy class in. + */ + protected Class resolveProxyClass(String[] interfaces) + throws IOException, ClassNotFoundException + { + /* + * Always read annotation written by MarshalOutputStream. + */ + Object annotation = readLocation(); + + ClassLoader defaultLoader = + skipDefaultResolveClass ? null : latestUserDefinedLoader(); + + String codebase = null; + if (!useCodebaseOnly && annotation instanceof String) { + codebase = (String) annotation; + } + + return RMIClassLoader.loadProxyClass(codebase, interfaces, + defaultLoader); + } + + /* + * Returns the first non-null class loader up the execution stack, or null + * if only code from the null class loader is on the stack. + */ + private static native ClassLoader latestUserDefinedLoader(); + + /** + * Fix for 4179055: Need to assist resolving sun stubs; resolve + * class locally if it is a "permitted" sun class + */ + private Class checkSunClass(String className, AccessControlException e) + throws AccessControlException + { + // ensure that we are giving out a stub for the correct reason + Permission perm = e.getPermission(); + String name = null; + if (perm != null) { + name = perm.getName(); + } + + Class resolvedClass = + (Class) permittedSunClasses.get(className); + + // if class not permitted, throw the SecurityException + if ((name == null) || + (resolvedClass == null) || + ((!name.equals("accessClassInPackage.sun.rmi.server")) && + (!name.equals("accessClassInPackage.sun.rmi.registry")))) + { + throw e; + } + + return resolvedClass; + } + + /** + * Return the location for the class in the stream. This method can + * be overridden by subclasses that store this annotation somewhere + * else than as the next object in the stream, as is done by this class. + */ + protected Object readLocation() + throws IOException, ClassNotFoundException + { + return readObject(); + } + + /** + * Set a flag to indicate that the superclass's default resolveClass() + * implementation should not be invoked by our resolveClass(). + */ + void skipDefaultResolveClass() { + skipDefaultResolveClass = true; + } + + /** + * Disable code downloading except from the URL specified by the + * "java.rmi.server.codebase" property. + */ + void useCodebaseOnly() { + useCodebaseOnly = true; + } +} diff --git a/src/share/classes/sun/rmi/server/MarshalOutputStream.java b/src/share/classes/sun/rmi/server/MarshalOutputStream.java new file mode 100644 index 000000000..63aed2bee --- /dev/null +++ b/src/share/classes/sun/rmi/server/MarshalOutputStream.java @@ -0,0 +1,111 @@ +/* + * Copyright 1996-2004 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.server; + +import java.io.*; +import java.rmi.Remote; +import java.rmi.server.RemoteStub; +import sun.rmi.transport.ObjectTable; +import sun.rmi.transport.Target; + +/** + * A MarshalOutputStream extends ObjectOutputStream to add functions + * specific to marshaling of remote object references. If it is + * necessary to serialize remote objects or objects that contain + * references to remote objects a MarshalOutputStream must be used + * instead of ObjectOutputStream. <p> + * + * A new MarshalOutputStream is constructed to serialize remote + * objects or graphs containing remote objects. Objects are written to + * the stream using the ObjectOutputStream.writeObject method. <p> + * + * MarshalOutputStream maps remote objects to the corresponding remote + * stub and embeds the location from which to load the stub + * classes. The location may be ignored by the client but is supplied. + */ +public class MarshalOutputStream extends ObjectOutputStream +{ + /** + * Creates a marshal output stream with protocol version 1. + */ + public MarshalOutputStream(OutputStream out) throws IOException { + this(out, ObjectStreamConstants.PROTOCOL_VERSION_1); + } + + /** + * Creates a marshal output stream with the given protocol version. + */ + public MarshalOutputStream(OutputStream out, int protocolVersion) + throws IOException + { + super(out); + this.useProtocolVersion(protocolVersion); + java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + enableReplaceObject(true); + return null; + } + }); + } + + /** + * Checks for objects that are instances of java.rmi.Remote + * that need to be serialized as proxy objects. + */ + protected final Object replaceObject(Object obj) throws IOException { + if ((obj instanceof Remote) && !(obj instanceof RemoteStub)) { + Target target = ObjectTable.getTarget((Remote) obj); + if (target != null) { + return target.getStub(); + } + } + return obj; + } + + /** + * Serializes a location from which to load the the specified class. + */ + protected void annotateClass(Class<?> cl) throws IOException { + writeLocation(java.rmi.server.RMIClassLoader.getClassAnnotation(cl)); + } + + /** + * Serializes a location from which to load the specified class. + */ + protected void annotateProxyClass(Class<?> cl) throws IOException { + annotateClass(cl); + } + + /** + * Writes the location for the class into the stream. This method can + * be overridden by subclasses that store this annotation somewhere + * else than as the next object in the stream, as is done by this class. + */ + protected void writeLocation(String location) throws IOException { + writeObject(location); + } +} diff --git a/src/share/classes/sun/rmi/server/UnicastRef.java b/src/share/classes/sun/rmi/server/UnicastRef.java new file mode 100644 index 000000000..b9307f9ea --- /dev/null +++ b/src/share/classes/sun/rmi/server/UnicastRef.java @@ -0,0 +1,516 @@ +/* + * Copyright 1996-2006 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.server; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.lang.reflect.Method; +import java.rmi.MarshalException; +import java.rmi.Remote; +import java.rmi.RemoteException; +import java.rmi.ServerException; +import java.rmi.UnmarshalException; +import java.rmi.server.Operation; +import java.rmi.server.RemoteCall; +import java.rmi.server.RemoteObject; +import java.rmi.server.RemoteRef; +import java.security.AccessController; +import sun.rmi.runtime.Log; +import sun.rmi.transport.Connection; +import sun.rmi.transport.LiveRef; +import sun.rmi.transport.StreamRemoteCall; +import sun.security.action.GetBooleanAction; + +/** + * NOTE: There is a JDK-internal dependency on the existence of this + * class's getLiveRef method (as it is inherited by UnicastRef2) in + * the implementation of javax.management.remote.rmi.RMIConnector. + **/ +public class UnicastRef implements RemoteRef { + + /** + * Client-side transport log. + */ + public static final Log clientRefLog = + Log.getLog("sun.rmi.client.ref", "transport", Util.logLevel); + + /** + * Client-side call log. + */ + public static final Log clientCallLog = + Log.getLog("sun.rmi.client.call", "RMI", + AccessController.doPrivileged( + new GetBooleanAction("sun.rmi.client.logCalls"))); + + protected LiveRef ref; + + /** + * Create a new (empty) Unicast remote reference. + */ + public UnicastRef() { + } + + /** + * Create a new Unicast RemoteRef. + */ + public UnicastRef(LiveRef liveRef) { + ref = liveRef; + } + + /** + * Returns the current value of this UnicastRef's underlying + * LiveRef. + * + * NOTE: There is a JDK-internal dependency on the existence of + * this method (as it is inherited by UnicastRef) in the + * implementation of javax.management.remote.rmi.RMIConnector. + **/ + public LiveRef getLiveRef() { + return ref; + } + + /** + * Invoke a method. This form of delegating method invocation + * to the reference allows the reference to take care of + * setting up the connection to the remote host, marshalling + * some representation for the method and parameters, then + * communicating the method invocation to the remote host. + * This method either returns the result of a method invocation + * on the remote object which resides on the remote host or + * throws a RemoteException if the call failed or an + * application-level exception if the remote invocation throws + * an exception. + * + * @param obj the proxy for the remote object + * @param method the method to be invoked + * @param params the parameter list + * @param opnum a hash that may be used to represent the method + * @since 1.2 + */ + public Object invoke(Remote obj, + Method method, + Object[] params, + long opnum) + throws Exception + { + if (clientRefLog.isLoggable(Log.VERBOSE)) { + clientRefLog.log(Log.VERBOSE, "method: " + method); + } + + if (clientCallLog.isLoggable(Log.VERBOSE)) { + logClientCall(obj, method); + } + + Connection conn = ref.getChannel().newConnection(); + RemoteCall call = null; + boolean reuse = true; + + /* If the call connection is "reused" early, remember not to + * reuse again. + */ + boolean alreadyFreed = false; + + try { + if (clientRefLog.isLoggable(Log.VERBOSE)) { + clientRefLog.log(Log.VERBOSE, "opnum = " + opnum); + } + + // create call context + call = new StreamRemoteCall(conn, ref.getObjID(), -1, opnum); + + // marshal parameters + try { + ObjectOutput out = call.getOutputStream(); + marshalCustomCallData(out); + Class<?>[] types = method.getParameterTypes(); + for (int i = 0; i < types.length; i++) { + marshalValue(types[i], params[i], out); + } + } catch (IOException e) { + clientRefLog.log(Log.BRIEF, + "IOException marshalling arguments: ", e); + throw new MarshalException("error marshalling arguments", e); + } + + // unmarshal return + call.executeCall(); + + try { + Class<?> rtype = method.getReturnType(); + if (rtype == void.class) + return null; + ObjectInput in = call.getInputStream(); + + /* StreamRemoteCall.done() does not actually make use + * of conn, therefore it is safe to reuse this + * connection before the dirty call is sent for + * registered refs. + */ + Object returnValue = unmarshalValue(rtype, in); + + /* we are freeing the connection now, do not free + * again or reuse. + */ + alreadyFreed = true; + + /* if we got to this point, reuse must have been true. */ + clientRefLog.log(Log.BRIEF, "free connection (reuse = true)"); + + /* Free the call's connection early. */ + ref.getChannel().free(conn, true); + + return returnValue; + + } catch (IOException e) { + clientRefLog.log(Log.BRIEF, + "IOException unmarshalling return: ", e); + throw new UnmarshalException("error unmarshalling return", e); + } catch (ClassNotFoundException e) { + clientRefLog.log(Log.BRIEF, + "ClassNotFoundException unmarshalling return: ", e); + + throw new UnmarshalException("error unmarshalling return", e); + } finally { + try { + call.done(); + } catch (IOException e) { + /* WARNING: If the conn has been reused early, + * then it is too late to recover from thrown + * IOExceptions caught here. This code is relying + * on StreamRemoteCall.done() not actually + * throwing IOExceptions. + */ + reuse = false; + } + } + + } catch (RuntimeException e) { + /* + * Need to distinguish between client (generated by the + * invoke method itself) and server RuntimeExceptions. + * Client side RuntimeExceptions are likely to have + * corrupted the call connection and those from the server + * are not likely to have done so. If the exception came + * from the server the call connection should be reused. + */ + if ((call == null) || + (((StreamRemoteCall) call).getServerException() != e)) + { + reuse = false; + } + throw e; + + } catch (RemoteException e) { + /* + * Some failure during call; assume connection cannot + * be reused. Must assume failure even if ServerException + * or ServerError occurs since these failures can happen + * during parameter deserialization which would leave + * the connection in a corrupted state. + */ + reuse = false; + throw e; + + } catch (Error e) { + /* If errors occurred, the connection is most likely not + * reusable. + */ + reuse = false; + throw e; + + } finally { + + /* alreadyFreed ensures that we do not log a reuse that + * may have already happened. + */ + if (!alreadyFreed) { + if (clientRefLog.isLoggable(Log.BRIEF)) { + clientRefLog.log(Log.BRIEF, "free connection (reuse = " + + reuse + ")"); + } + ref.getChannel().free(conn, reuse); + } + } + } + + protected void marshalCustomCallData(ObjectOutput out) throws IOException + {} + + /** + * Marshal value to an ObjectOutput sink using RMI's serialization + * format for parameters or return values. + */ + protected static void marshalValue(Class<?> type, Object value, + ObjectOutput out) + throws IOException + { + if (type.isPrimitive()) { + if (type == int.class) { + out.writeInt(((Integer) value).intValue()); + } else if (type == boolean.class) { + out.writeBoolean(((Boolean) value).booleanValue()); + } else if (type == byte.class) { + out.writeByte(((Byte) value).byteValue()); + } else if (type == char.class) { + out.writeChar(((Character) value).charValue()); + } else if (type == short.class) { + out.writeShort(((Short) value).shortValue()); + } else if (type == long.class) { + out.writeLong(((Long) value).longValue()); + } else if (type == float.class) { + out.writeFloat(((Float) value).floatValue()); + } else if (type == double.class) { + out.writeDouble(((Double) value).doubleValue()); + } else { + throw new Error("Unrecognized primitive type: " + type); + } + } else { + out.writeObject(value); + } + } + + /** + * Unmarshal value from an ObjectInput source using RMI's serialization + * format for parameters or return values. + */ + protected static Object unmarshalValue(Class<?> type, ObjectInput in) + throws IOException, ClassNotFoundException + { + if (type.isPrimitive()) { + if (type == int.class) { + return Integer.valueOf(in.readInt()); + } else if (type == boolean.class) { + return Boolean.valueOf(in.readBoolean()); + } else if (type == byte.class) { + return Byte.valueOf(in.readByte()); + } else if (type == char.class) { + return Character.valueOf(in.readChar()); + } else if (type == short.class) { + return Short.valueOf(in.readShort()); + } else if (type == long.class) { + return Long.valueOf(in.readLong()); + } else if (type == float.class) { + return Float.valueOf(in.readFloat()); + } else if (type == double.class) { + return Double.valueOf(in.readDouble()); + } else { + throw new Error("Unrecognized primitive type: " + type); + } + } else { + return in.readObject(); + } + } + + /** + * Create an appropriate call object for a new call on this object. + * Passing operation array and index, allows the stubs generator to + * assign the operation indexes and interpret them. The RemoteRef + * may need the operation to encode in for the call. + */ + public RemoteCall newCall(RemoteObject obj, Operation[] ops, int opnum, + long hash) + throws RemoteException + { + clientRefLog.log(Log.BRIEF, "get connection"); + + Connection conn = ref.getChannel().newConnection(); + try { + clientRefLog.log(Log.VERBOSE, "create call context"); + + /* log information about the outgoing call */ + if (clientCallLog.isLoggable(Log.VERBOSE)) { + logClientCall(obj, ops[opnum]); + } + + RemoteCall call = + new StreamRemoteCall(conn, ref.getObjID(), opnum, hash); + try { + marshalCustomCallData(call.getOutputStream()); + } catch (IOException e) { + throw new MarshalException("error marshaling " + + "custom call data"); + } + return call; + } catch (RemoteException e) { + ref.getChannel().free(conn, false); + throw e; + } + } + + /** + * Invoke makes the remote call present in the RemoteCall object. + * + * Invoke will raise any "user" exceptions which + * should pass through and not be caught by the stub. If any + * exception is raised during the remote invocation, invoke should + * take care of cleaning up the connection before raising the + * "user" or remote exception. + */ + public void invoke(RemoteCall call) throws Exception { + try { + clientRefLog.log(Log.VERBOSE, "execute call"); + + call.executeCall(); + + } catch (RemoteException e) { + /* + * Call did not complete; connection can't be reused. + */ + clientRefLog.log(Log.BRIEF, "exception: ", e); + free(call, false); + throw e; + + } catch (Error e) { + /* If errors occurred, the connection is most likely not + * reusable. + */ + clientRefLog.log(Log.BRIEF, "error: ", e); + free(call, false); + throw e; + + } catch (RuntimeException e) { + /* + * REMIND: Since runtime exceptions are no longer wrapped, + * we can't assue that the connection was left in + * a reusable state. Is this okay? + */ + clientRefLog.log(Log.BRIEF, "exception: ", e); + free(call, false); + throw e; + + } catch (Exception e) { + /* + * Assume that these other exceptions are user exceptions + * and leave the connection in a reusable state. + */ + clientRefLog.log(Log.BRIEF, "exception: ", e); + free(call, true); + /* reraise user (and unknown) exceptions. */ + throw e; + } + + /* + * Don't free the connection if an exception did not + * occur because the stub needs to unmarshal the + * return value. The connection will be freed + * by a call to the "done" method. + */ + } + + /** + * Private method to free a connection. + */ + private void free(RemoteCall call, boolean reuse) throws RemoteException { + Connection conn = ((StreamRemoteCall)call).getConnection(); + ref.getChannel().free(conn, reuse); + } + + /** + * Done should only be called if the invoke returns successfully + * (non-exceptionally) to the stub. It allows the remote reference to + * clean up (or reuse) the connection. + */ + public void done(RemoteCall call) throws RemoteException { + + /* Done only uses the connection inside the call to obtain the + * channel the connection uses. Once all information is read + * from the connection, the connection may be freed. + */ + clientRefLog.log(Log.BRIEF, "free connection (reuse = true)"); + + /* Free the call connection early. */ + free(call, true); + + try { + call.done(); + } catch (IOException e) { + /* WARNING: If the conn has been reused early, then it is + * too late to recover from thrown IOExceptions caught + * here. This code is relying on StreamRemoteCall.done() + * not actually throwing IOExceptions. + */ + } + } + + /** + * Log the details of an outgoing call. The method parameter is either of + * type java.lang.reflect.Method or java.rmi.server.Operation. + */ + void logClientCall(Object obj, Object method) { + clientCallLog.log(Log.VERBOSE, "outbound call: " + + ref + " : " + obj.getClass().getName() + + ref.getObjID().toString() + ": " + method); + } + + /** + * Returns the class of the ref type to be serialized + */ + public String getRefClass(ObjectOutput out) { + return "UnicastRef"; + } + + /** + * Write out external representation for remote ref. + */ + public void writeExternal(ObjectOutput out) throws IOException { + ref.write(out, false); + } + + /** + * Read in external representation for remote ref. + * @exception ClassNotFoundException If the class for an object + * being restored cannot be found. + */ + public void readExternal(ObjectInput in) + throws IOException, ClassNotFoundException + { + ref = LiveRef.read(in, false); + } + + //----------------------------------------------------------------------; + /** + * Method from object, forward from RemoteObject + */ + public String remoteToString() { + return Util.getUnqualifiedName(getClass()) + " [liveRef: " + ref + "]"; + } + + /** + * default implementation of hashCode for remote objects + */ + public int remoteHashCode() { + return ref.hashCode(); + } + + /** default implementation of equals for remote objects + */ + public boolean remoteEquals(RemoteRef sub) { + if (sub instanceof UnicastRef) + return ref.remoteEquals(((UnicastRef)sub).ref); + return false; + } +} diff --git a/src/share/classes/sun/rmi/server/UnicastRef2.java b/src/share/classes/sun/rmi/server/UnicastRef2.java new file mode 100644 index 000000000..011a27e50 --- /dev/null +++ b/src/share/classes/sun/rmi/server/UnicastRef2.java @@ -0,0 +1,79 @@ +/* + * Copyright 1997-2005 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.server; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import sun.rmi.transport.LiveRef; + +/** + * NOTE: There is a JDK-internal dependency on the existence of this + * class and its getLiveRef method (inherited from UnicastRef) in the + * implementation of javax.management.remote.rmi.RMIConnector. + **/ +public class UnicastRef2 extends UnicastRef { + + /** + * Create a new (empty) Unicast remote reference. + */ + public UnicastRef2() + {} + + /** + * Create a new Unicast RemoteRef. + */ + public UnicastRef2(LiveRef liveRef) { + super(liveRef); + } + + /** + * Returns the class of the ref type to be serialized + */ + public String getRefClass(ObjectOutput out) + { + return "UnicastRef2"; + } + + /** + * Write out external representation for remote ref. + */ + public void writeExternal(ObjectOutput out) throws IOException + { + ref.write(out, true); + } + + /** + * Read in external representation for remote ref. + * @exception ClassNotFoundException If the class for an object + * being restored cannot be found. + */ + public void readExternal(ObjectInput in) + throws IOException, ClassNotFoundException + { + ref = LiveRef.read(in, true); + } +} diff --git a/src/share/classes/sun/rmi/server/UnicastServerRef.java b/src/share/classes/sun/rmi/server/UnicastServerRef.java new file mode 100644 index 000000000..63510db4f --- /dev/null +++ b/src/share/classes/sun/rmi/server/UnicastServerRef.java @@ -0,0 +1,563 @@ +/* + * Copyright 1996-2005 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.server; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.PrintStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.rmi.MarshalException; +import java.rmi.Remote; +import java.rmi.RemoteException; +import java.rmi.ServerError; +import java.rmi.ServerException; +import java.rmi.UnmarshalException; +import java.rmi.server.ExportException; +import java.rmi.server.RemoteCall; +import java.rmi.server.RemoteRef; +import java.rmi.server.RemoteStub; +import java.rmi.server.ServerNotActiveException; +import java.rmi.server.ServerRef; +import java.rmi.server.Skeleton; +import java.rmi.server.SkeletonNotFoundException; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.WeakHashMap; +import sun.rmi.runtime.Log; +import sun.rmi.transport.LiveRef; +import sun.rmi.transport.Target; +import sun.rmi.transport.tcp.TCPTransport; +import sun.security.action.GetBooleanAction; + +/** + * UnicastServerRef implements the remote reference layer server-side + * behavior for remote objects exported with the "UnicastRef" reference + * type. + * + * @author Ann Wollrath + * @author Roger Riggs + * @author Peter Jones + */ +public class UnicastServerRef extends UnicastRef + implements ServerRef, Dispatcher +{ + /** value of server call log property */ + public static final boolean logCalls = AccessController.doPrivileged( + new GetBooleanAction("java.rmi.server.logCalls")); + + /** server call log */ + public static final Log callLog = + Log.getLog("sun.rmi.server.call", "RMI", logCalls); + + // use serialVersionUID from JDK 1.2.2 for interoperability + private static final long serialVersionUID = -7384275867073752268L; + + /** flag to enable writing exceptions to System.err */ + private static final boolean wantExceptionLog = + AccessController.doPrivileged( + new GetBooleanAction("sun.rmi.server.exceptionTrace")); + + private boolean forceStubUse = false; + + /** + * flag to remove server-side stack traces before marshalling + * exceptions thrown by remote invocations to this VM + */ + private static final boolean suppressStackTraces = + AccessController.doPrivileged( + new GetBooleanAction( + "sun.rmi.server.suppressStackTraces")); + + /** + * skeleton to dispatch remote calls through, for 1.1 stub protocol + * (may be null if stub class only uses 1.2 stub protocol) + */ + private transient Skeleton skel; + + /** maps method hash to Method object for each remote method */ + private transient Map<Long,Method> hashToMethod_Map = null; + + /** + * A weak hash map, mapping classes to hash maps that map method + * hashes to method objects. + **/ + private static final WeakClassHashMap<Map<Long,Method>> hashToMethod_Maps = + new HashToMethod_Maps(); + + /** cache of impl classes that have no corresponding skeleton class */ + private static final Map<Class<?>,?> withoutSkeletons = + Collections.synchronizedMap(new WeakHashMap<Class<?>,Void>()); + + /** + * Create a new (empty) Unicast server remote reference. + */ + public UnicastServerRef() { + } + + /** + * Construct a Unicast server remote reference for a specified + * liveRef. + */ + public UnicastServerRef(LiveRef ref) { + super(ref); + } + + /** + * Construct a Unicast server remote reference to be exported + * on the specified port. + */ + public UnicastServerRef(int port) { + super(new LiveRef(port)); + } + + /** + * Constructs a UnicastServerRef to be exported on an + * anonymous port (i.e., 0) and that uses a pregenerated stub class + * (NOT a dynamic proxy instance) if 'forceStubUse' is 'true'. + * + * This constructor is only called by the method + * UnicastRemoteObject.exportObject(Remote) passing 'true' for + * 'forceStubUse'. The UnicastRemoteObject.exportObject(Remote) method + * returns RemoteStub, so it must ensure that the stub for the + * exported object is an instance of a pregenerated stub class that + * extends RemoteStub (instead of an instance of a dynamic proxy class + * which is not an instance of RemoteStub). + **/ + public UnicastServerRef(boolean forceStubUse) { + this(0); + this.forceStubUse = forceStubUse; + } + + /** + * With the addition of support for dynamic proxies as stubs, this + * method is obsolete because it returns RemoteStub instead of the more + * general Remote. It should not be called. It sets the + * 'forceStubUse' flag to true so that the stub for the exported object + * is forced to be an instance of the pregenerated stub class, which + * extends RemoteStub. + * + * Export this object, create the skeleton and stubs for this + * dispatcher. Create a stub based on the type of the impl, + * initialize it with the appropriate remote reference. Create the + * target defined by the impl, dispatcher (this) and stub. + * Export that target via the Ref. + **/ + public RemoteStub exportObject(Remote impl, Object data) + throws RemoteException + { + forceStubUse = true; + return (RemoteStub) exportObject(impl, data, false); + } + + /** + * Export this object, create the skeleton and stubs for this + * dispatcher. Create a stub based on the type of the impl, + * initialize it with the appropriate remote reference. Create the + * target defined by the impl, dispatcher (this) and stub. + * Export that target via the Ref. + */ + public Remote exportObject(Remote impl, Object data, + boolean permanent) + throws RemoteException + { + Class implClass = impl.getClass(); + Remote stub; + + try { + stub = Util.createProxy(implClass, getClientRef(), forceStubUse); + } catch (IllegalArgumentException e) { + throw new ExportException( + "remote object implements illegal remote interface", e); + } + if (stub instanceof RemoteStub) { + setSkeleton(impl); + } + + Target target = + new Target(impl, this, stub, ref.getObjID(), permanent); + ref.exportObject(target); + hashToMethod_Map = hashToMethod_Maps.get(implClass); + return stub; + } + + /** + * Return the hostname of the current client. When called from a + * thread actively handling a remote method invocation the + * hostname of the client is returned. + * @exception ServerNotActiveException If called outside of servicing + * a remote method invocation. + */ + public String getClientHost() throws ServerNotActiveException { + return TCPTransport.getClientHost(); + } + + /** + * Discovers and sets the appropriate skeleton for the impl. + */ + public void setSkeleton(Remote impl) throws RemoteException { + if (!withoutSkeletons.containsKey(impl.getClass())) { + try { + skel = Util.createSkeleton(impl); + } catch (SkeletonNotFoundException e) { + /* + * Ignore exception for skeleton class not found, because a + * skeleton class is not necessary with the 1.2 stub protocol. + * Remember that this impl's class does not have a skeleton + * class so we don't waste time searching for it again. + */ + withoutSkeletons.put(impl.getClass(), null); + } + } + } + + /** + * Call to dispatch to the remote object (on the server side). + * The up-call to the server and the marshalling of return result + * (or exception) should be handled before returning from this + * method. + * @param obj the target remote object for the call + * @param call the "remote call" from which operation and + * method arguments can be obtained. + * @exception IOException If unable to marshal return result or + * release input or output streams + */ + public void dispatch(Remote obj, RemoteCall call) throws IOException { + // positive operation number in 1.1 stubs; + // negative version number in 1.2 stubs and beyond... + int num; + long op; + + try { + // read remote call header + ObjectInput in; + try { + in = call.getInputStream(); + num = in.readInt(); + if (num >= 0) { + if (skel != null) { + oldDispatch(obj, call, num); + return; + } else { + throw new UnmarshalException( + "skeleton class not found but required " + + "for client version"); + } + } + op = in.readLong(); + } catch (Exception readEx) { + throw new UnmarshalException("error unmarshalling call header", + readEx); + } + + /* + * Since only system classes (with null class loaders) will be on + * the execution stack during parameter unmarshalling for the 1.2 + * stub protocol, tell the MarshalInputStream not to bother trying + * to resolve classes using its superclasses's default method of + * consulting the first non-null class loader on the stack. + */ + MarshalInputStream marshalStream = (MarshalInputStream) in; + marshalStream.skipDefaultResolveClass(); + + Method method = hashToMethod_Map.get(op); + if (method == null) { + throw new UnmarshalException("unrecognized method hash: " + + "method not supported by remote object"); + } + + // if calls are being logged, write out object id and operation + logCall(obj, method); + + // unmarshal parameters + Class[] types = method.getParameterTypes(); + Object[] params = new Object[types.length]; + + try { + unmarshalCustomCallData(in); + for (int i = 0; i < types.length; i++) { + params[i] = unmarshalValue(types[i], in); + } + } catch (java.io.IOException e) { + throw new UnmarshalException( + "error unmarshalling arguments", e); + } catch (ClassNotFoundException e) { + throw new UnmarshalException( + "error unmarshalling arguments", e); + } finally { + call.releaseInputStream(); + } + + // make upcall on remote object + Object result; + try { + result = method.invoke(obj, params); + } catch (InvocationTargetException e) { + throw e.getTargetException(); + } + + // marshal return value + try { + ObjectOutput out = call.getResultStream(true); + Class rtype = method.getReturnType(); + if (rtype != void.class) { + marshalValue(rtype, result, out); + } + } catch (IOException ex) { + throw new MarshalException("error marshalling return", ex); + /* + * This throw is problematic because when it is caught below, + * we attempt to marshal it back to the client, but at this + * point, a "normal return" has already been indicated, + * so marshalling an exception will corrupt the stream. + * This was the case with skeletons as well; there is no + * immediately obvious solution without a protocol change. + */ + } + } catch (Throwable e) { + logCallException(e); + + ObjectOutput out = call.getResultStream(false); + if (e instanceof Error) { + e = new ServerError( + "Error occurred in server thread", (Error) e); + } else if (e instanceof RemoteException) { + e = new ServerException( + "RemoteException occurred in server thread", + (Exception) e); + } + if (suppressStackTraces) { + clearStackTraces(e); + } + out.writeObject(e); + } finally { + call.releaseInputStream(); // in case skeleton doesn't + call.releaseOutputStream(); + } + } + + protected void unmarshalCustomCallData(ObjectInput in) + throws IOException, ClassNotFoundException + {} + + /** + * Handle server-side dispatch using the RMI 1.1 stub/skeleton + * protocol, given a non-negative operation number that has + * already been read from the call stream. + * + * @param obj the target remote object for the call + * @param call the "remote call" from which operation and + * method arguments can be obtained. + * @param op the operation number + * @exception IOException if unable to marshal return result or + * release input or output streams + */ + public void oldDispatch(Remote obj, RemoteCall call, int op) + throws IOException + { + long hash; // hash for matching stub with skeleton + + try { + // read remote call header + ObjectInput in; + try { + in = call.getInputStream(); + hash = in.readLong(); + } catch (Exception readEx) { + throw new UnmarshalException("error unmarshalling call header", + readEx); + } + + // if calls are being logged, write out object id and operation + logCall(obj, skel.getOperations()[op]); + unmarshalCustomCallData(in); + // dispatch to skeleton for remote object + skel.dispatch(obj, call, op, hash); + + } catch (Throwable e) { + logCallException(e); + + ObjectOutput out = call.getResultStream(false); + if (e instanceof Error) { + e = new ServerError( + "Error occurred in server thread", (Error) e); + } else if (e instanceof RemoteException) { + e = new ServerException( + "RemoteException occurred in server thread", + (Exception) e); + } + if (suppressStackTraces) { + clearStackTraces(e); + } + out.writeObject(e); + } finally { + call.releaseInputStream(); // in case skeleton doesn't + call.releaseOutputStream(); + } + } + + /** + * Clear the stack trace of the given Throwable by replacing it with + * an empty StackTraceElement array, and do the same for all of its + * chained causative exceptions. + */ + public static void clearStackTraces(Throwable t) { + StackTraceElement[] empty = new StackTraceElement[0]; + while (t != null) { + t.setStackTrace(empty); + t = t.getCause(); + } + } + + /** + * Log the details of an incoming call. The method parameter is either of + * type java.lang.reflect.Method or java.rmi.server.Operation. + */ + private void logCall(Remote obj, Object method) { + if (callLog.isLoggable(Log.VERBOSE)) { + String clientHost; + try { + clientHost = getClientHost(); + } catch (ServerNotActiveException snae) { + clientHost = "(local)"; // shouldn't happen + } + callLog.log(Log.VERBOSE, "[" + clientHost + ": " + + obj.getClass().getName() + + ref.getObjID().toString() + ": " + + method + "]"); + } + } + + /** + * Log the exception detail of an incoming call. + */ + private void logCallException(Throwable e) { + // if calls are being logged, log them + if (callLog.isLoggable(Log.BRIEF)) { + String clientHost = ""; + try { + clientHost = "[" + getClientHost() + "] "; + } catch (ServerNotActiveException snae) { + } + callLog.log(Log.BRIEF, clientHost + "exception: ", e); + } + + // write exceptions (only) to System.err if desired + if (wantExceptionLog) { + java.io.PrintStream log = System.err; + synchronized (log) { + log.println(); + log.println("Exception dispatching call to " + + ref.getObjID() + " in thread \"" + + Thread.currentThread().getName() + + "\" at " + (new Date()) + ":"); + e.printStackTrace(log); + } + } + } + + /** + * Returns the class of the ref type to be serialized. + */ + public String getRefClass(ObjectOutput out) { + return "UnicastServerRef"; + } + + /** + * Return the client remote reference for this remoteRef. + * In the case of a client RemoteRef "this" is the answer. + * For a server remote reference, a client side one will have to + * found or created. + */ + protected RemoteRef getClientRef() { + return new UnicastRef(ref); + } + + /** + * Write out external representation for remote ref. + */ + public void writeExternal(ObjectOutput out) throws IOException { + } + + /** + * Read in external representation for remote ref. + * @exception ClassNotFoundException If the class for an object + * being restored cannot be found. + */ + public void readExternal(ObjectInput in) + throws IOException, ClassNotFoundException + { + // object is re-exported elsewhere (e.g., by UnicastRemoteObject) + ref = null; + skel = null; + } + + + /** + * A weak hash map, mapping classes to hash maps that map method + * hashes to method objects. + **/ + private static class HashToMethod_Maps + extends WeakClassHashMap<Map<Long,Method>> + { + HashToMethod_Maps() {} + + protected Map<Long,Method> computeValue(Class<?> remoteClass) { + Map<Long,Method> map = new HashMap<Long,Method>(); + for (Class<?> cl = remoteClass; + cl != null; + cl = cl.getSuperclass()) + { + for (Class<?> intf : cl.getInterfaces()) { + if (Remote.class.isAssignableFrom(intf)) { + for (Method method : intf.getMethods()) { + final Method m = method; + /* + * Set this Method object to override language + * access checks so that the dispatcher can invoke + * methods from non-public remote interfaces. + */ + AccessController.doPrivileged( + new PrivilegedAction<Void>() { + public Void run() { + m.setAccessible(true); + return null; + } + }); + map.put(Util.computeMethodHash(m), m); + } + } + } + } + return map; + } + } +} diff --git a/src/share/classes/sun/rmi/server/UnicastServerRef2.java b/src/share/classes/sun/rmi/server/UnicastServerRef2.java new file mode 100644 index 000000000..d2877ee70 --- /dev/null +++ b/src/share/classes/sun/rmi/server/UnicastServerRef2.java @@ -0,0 +1,89 @@ +/* + * Copyright 1997-2002 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.server; + +import java.io.IOException; +import java.io.ObjectOutput; +import java.rmi.*; +import java.rmi.server.*; +import sun.rmi.transport.*; +import sun.rmi.transport.tcp.*; + +/** + * Server-side ref for a remote impl that uses a custom socket factory. + * + * @author Ann Wollrath + * @author Roger Riggs + */ +public class UnicastServerRef2 extends UnicastServerRef +{ + // use serialVersionUID from JDK 1.2.2 for interoperability + private static final long serialVersionUID = -2289703812660767614L; + + /** + * Create a new (empty) Unicast server remote reference. + */ + public UnicastServerRef2() + {} + + /** + * Construct a Unicast server remote reference for a specified + * liveRef. + */ + public UnicastServerRef2(LiveRef ref) + { + super(ref); + } + + /** + * Construct a Unicast server remote reference to be exported + * on the specified port. + */ + public UnicastServerRef2(int port, + RMIClientSocketFactory csf, + RMIServerSocketFactory ssf) + { + super(new LiveRef(port, csf, ssf)); + } + + /** + * Returns the class of the ref type to be serialized + */ + public String getRefClass(ObjectOutput out) + { + return "UnicastServerRef2"; + } + + /** + * Return the client remote reference for this remoteRef. + * In the case of a client RemoteRef "this" is the answer. + * For a server remote reference, a client side one will have to + * found or created. + */ + protected RemoteRef getClientRef() { + return new UnicastRef2(ref); + } +} diff --git a/src/share/classes/sun/rmi/server/Util.java b/src/share/classes/sun/rmi/server/Util.java new file mode 100644 index 000000000..f103319ce --- /dev/null +++ b/src/share/classes/sun/rmi/server/Util.java @@ -0,0 +1,461 @@ +/* + * Copyright 1996-2006 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ +package sun.rmi.server; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.DataOutputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Proxy; +import java.lang.reflect.Method; +import java.rmi.Remote; +import java.rmi.RemoteException; +import java.rmi.StubNotFoundException; +import java.rmi.registry.Registry; +import java.rmi.server.LogStream; +import java.rmi.server.ObjID; +import java.rmi.server.RMIClientSocketFactory; +import java.rmi.server.RemoteObjectInvocationHandler; +import java.rmi.server.RemoteRef; +import java.rmi.server.RemoteStub; +import java.rmi.server.Skeleton; +import java.rmi.server.SkeletonNotFoundException; +import java.security.AccessController; +import java.security.MessageDigest; +import java.security.DigestOutputStream; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Map; +import java.util.WeakHashMap; +import sun.rmi.registry.RegistryImpl; +import sun.rmi.runtime.Log; +import sun.rmi.transport.LiveRef; +import sun.rmi.transport.tcp.TCPEndpoint; +import sun.security.action.GetBooleanAction; +import sun.security.action.GetPropertyAction; + +/** + * A utility class with static methods for creating stubs/proxies and + * skeletons for remote objects. + */ +public final class Util { + + /** "server" package log level */ + static final int logLevel = LogStream.parseLevel( + (String) AccessController.doPrivileged( + new GetPropertyAction("sun.rmi.server.logLevel"))); + + /** server reference log */ + public static final Log serverRefLog = + Log.getLog("sun.rmi.server.ref", "transport", Util.logLevel); + + /** cached value of property java.rmi.server.ignoreStubClasses */ + private static final boolean ignoreStubClasses = + ((Boolean) AccessController.doPrivileged( + new GetBooleanAction("java.rmi.server.ignoreStubClasses"))). + booleanValue(); + + /** cache of impl classes that have no corresponding stub class */ + private static final Map withoutStubs = + Collections.synchronizedMap(new WeakHashMap(11)); + + /** parameter types for stub constructor */ + private static final Class[] stubConsParamTypes = { RemoteRef.class }; + + private Util() { + } + + /** + * Returns a proxy for the specified implClass. + * + * If both of the following criteria is satisfied, a dynamic proxy for + * the specified implClass is returned (otherwise a RemoteStub instance + * for the specified implClass is returned): + * + * a) either the property java.rmi.server.ignoreStubClasses is true or + * a pregenerated stub class does not exist for the impl class, and + * b) forceStubUse is false. + * + * If the above criteria are satisfied, this method constructs a + * dynamic proxy instance (that implements the remote interfaces of + * implClass) constructed with a RemoteObjectInvocationHandler instance + * constructed with the clientRef. + * + * Otherwise, this method loads the pregenerated stub class (which + * extends RemoteStub and implements the remote interfaces of + * implClass) and constructs an instance of the pregenerated stub + * class with the clientRef. + * + * @param implClass the class to obtain remote interfaces from + * @param clientRef the remote ref to use in the invocation handler + * @param forceStubUse if true, forces creation of a RemoteStub + * @throws IllegalArgumentException if implClass implements illegal + * remote interfaces + * @throws StubNotFoundException if problem locating/creating stub or + * creating the dynamic proxy instance + **/ + public static Remote createProxy(Class implClass, + RemoteRef clientRef, + boolean forceStubUse) + throws StubNotFoundException + { + Class remoteClass; + + try { + remoteClass = getRemoteClass(implClass); + } catch (ClassNotFoundException ex ) { + throw new StubNotFoundException( + "object does not implement a remote interface: " + + implClass.getName()); + } + + if (forceStubUse || + !(ignoreStubClasses || !stubClassExists(remoteClass))) + { + return createStub(remoteClass, clientRef); + } + + ClassLoader loader = implClass.getClassLoader(); + Class[] interfaces = getRemoteInterfaces(implClass); + InvocationHandler handler = + new RemoteObjectInvocationHandler(clientRef); + + /* REMIND: private remote interfaces? */ + + try { + return (Remote) Proxy.newProxyInstance(loader, + interfaces, + handler); + } catch (IllegalArgumentException e) { + throw new StubNotFoundException("unable to create proxy", e); + } + } + + /** + * Returns true if a stub class for the given impl class can be loaded, + * otherwise returns false. + * + * @param remoteClass the class to obtain remote interfaces from + */ + private static boolean stubClassExists(Class remoteClass) { + if (!withoutStubs.containsKey(remoteClass)) { + try { + Class.forName(remoteClass.getName() + "_Stub", + false, + remoteClass.getClassLoader()); + return true; + + } catch (ClassNotFoundException cnfe) { + withoutStubs.put(remoteClass, null); + } + } + return false; + } + + /* + * Returns the class/superclass that implements the remote interface. + * @throws ClassNotFoundException if no class is found to have a + * remote interface + */ + private static Class getRemoteClass(Class cl) + throws ClassNotFoundException + { + while (cl != null) { + Class[] interfaces = cl.getInterfaces(); + for (int i = interfaces.length -1; i >= 0; i--) { + if (Remote.class.isAssignableFrom(interfaces[i])) + return cl; // this class implements remote object + } + cl = cl.getSuperclass(); + } + throw new ClassNotFoundException( + "class does not implement java.rmi.Remote"); + } + + /** + * Returns an array containing the remote interfaces implemented + * by the given class. + * + * @param remoteClass the class to obtain remote interfaces from + * @throws IllegalArgumentException if remoteClass implements + * any illegal remote interfaces + * @throws NullPointerException if remoteClass is null + */ + private static Class[] getRemoteInterfaces(Class remoteClass) { + ArrayList list = new ArrayList(); + getRemoteInterfaces(list, remoteClass); + return (Class []) list.toArray(new Class[list.size()]); + } + + /** + * Fills the given array list with the remote interfaces implemented + * by the given class. + * + * @throws IllegalArgumentException if the specified class implements + * any illegal remote interfaces + * @throws NullPointerException if the specified class or list is null + */ + private static void getRemoteInterfaces(ArrayList list, Class cl) { + Class superclass = cl.getSuperclass(); + if (superclass != null) { + getRemoteInterfaces(list, superclass); + } + + Class[] interfaces = cl.getInterfaces(); + for (int i = 0; i < interfaces.length; i++) { + Class intf = interfaces[i]; + /* + * If it is a remote interface (if it extends from + * java.rmi.Remote) and is not already in the list, + * then add the interface to the list. + */ + if (Remote.class.isAssignableFrom(intf)) { + if (!(list.contains(intf))) { + Method[] methods = intf.getMethods(); + for (int j = 0; j < methods.length; j++) { + checkMethod(methods[j]); + } + list.add(intf); + } + } + } + } + + /** + * Verifies that the supplied method has at least one declared exception + * type that is RemoteException or one of its superclasses. If not, + * then this method throws IllegalArgumentException. + * + * @throws IllegalArgumentException if m is an illegal remote method + */ + private static void checkMethod(Method m) { + Class[] ex = m.getExceptionTypes(); + for (int i = 0; i < ex.length; i++) { + if (ex[i].isAssignableFrom(RemoteException.class)) + return; + } + throw new IllegalArgumentException( + "illegal remote method encountered: " + m); + } + + /** + * Creates a RemoteStub instance for the specified class, constructed + * with the specified RemoteRef. The supplied class must be the most + * derived class in the remote object's superclass chain that + * implements a remote interface. The stub class name is the name of + * the specified remoteClass with the suffix "_Stub". The loading of + * the stub class is initiated from class loader of the specified class + * (which may be the bootstrap class loader). + **/ + private static RemoteStub createStub(Class remoteClass, RemoteRef ref) + throws StubNotFoundException + { + String stubname = remoteClass.getName() + "_Stub"; + + /* Make sure to use the local stub loader for the stub classes. + * When loaded by the local loader the load path can be + * propagated to remote clients, by the MarshalOutputStream/InStream + * pickle methods + */ + try { + Class stubcl = + Class.forName(stubname, false, remoteClass.getClassLoader()); + Constructor cons = stubcl.getConstructor(stubConsParamTypes); + return (RemoteStub) cons.newInstance(new Object[] { ref }); + + } catch (ClassNotFoundException e) { + throw new StubNotFoundException( + "Stub class not found: " + stubname, e); + } catch (NoSuchMethodException e) { + throw new StubNotFoundException( + "Stub class missing constructor: " + stubname, e); + } catch (InstantiationException e) { + throw new StubNotFoundException( + "Can't create instance of stub class: " + stubname, e); + } catch (IllegalAccessException e) { + throw new StubNotFoundException( + "Stub class constructor not public: " + stubname, e); + } catch (InvocationTargetException e) { + throw new StubNotFoundException( + "Exception creating instance of stub class: " + stubname, e); + } catch (ClassCastException e) { + throw new StubNotFoundException( + "Stub class not instance of RemoteStub: " + stubname, e); + } + } + + /** + * Locate and return the Skeleton for the specified remote object + */ + static Skeleton createSkeleton(Remote object) + throws SkeletonNotFoundException + { + Class cl; + try { + cl = getRemoteClass(object.getClass()); + } catch (ClassNotFoundException ex ) { + throw new SkeletonNotFoundException( + "object does not implement a remote interface: " + + object.getClass().getName()); + } + + // now try to load the skeleton based ont he name of the class + String skelname = cl.getName() + "_Skel"; + try { + Class skelcl = Class.forName(skelname, false, cl.getClassLoader()); + + return (Skeleton)skelcl.newInstance(); + } catch (ClassNotFoundException ex) { + throw new SkeletonNotFoundException("Skeleton class not found: " + + skelname, ex); + } catch (InstantiationException ex) { + throw new SkeletonNotFoundException("Can't create skeleton: " + + skelname, ex); + } catch (IllegalAccessException ex) { + throw new SkeletonNotFoundException("No public constructor: " + + skelname, ex); + } catch (ClassCastException ex) { + throw new SkeletonNotFoundException( + "Skeleton not of correct class: " + skelname, ex); + } + } + + /** + * Compute the "method hash" of a remote method. The method hash + * is a long containing the first 64 bits of the SHA digest from + * the UTF encoded string of the method name and descriptor. + */ + public static long computeMethodHash(Method m) { + long hash = 0; + ByteArrayOutputStream sink = new ByteArrayOutputStream(127); + try { + MessageDigest md = MessageDigest.getInstance("SHA"); + DataOutputStream out = new DataOutputStream( + new DigestOutputStream(sink, md)); + + String s = getMethodNameAndDescriptor(m); + if (serverRefLog.isLoggable(Log.VERBOSE)) { + serverRefLog.log(Log.VERBOSE, + "string used for method hash: \"" + s + "\""); + } + out.writeUTF(s); + + // use only the first 64 bits of the digest for the hash + out.flush(); + byte hasharray[] = md.digest(); + for (int i = 0; i < Math.min(8, hasharray.length); i++) { + hash += ((long) (hasharray[i] & 0xFF)) << (i * 8); + } + } catch (IOException ignore) { + /* can't happen, but be deterministic anyway. */ + hash = -1; + } catch (NoSuchAlgorithmException complain) { + throw new SecurityException(complain.getMessage()); + } + return hash; + } + + /** + * Return a string consisting of the given method's name followed by + * its "method descriptor", as appropriate for use in the computation + * of the "method hash". + * + * See section 4.3.3 of The Java Virtual Machine Specification for + * the definition of a "method descriptor". + */ + private static String getMethodNameAndDescriptor(Method m) { + StringBuffer desc = new StringBuffer(m.getName()); + desc.append('('); + Class[] paramTypes = m.getParameterTypes(); + for (int i = 0; i < paramTypes.length; i++) { + desc.append(getTypeDescriptor(paramTypes[i])); + } + desc.append(')'); + Class returnType = m.getReturnType(); + if (returnType == void.class) { // optimization: handle void here + desc.append('V'); + } else { + desc.append(getTypeDescriptor(returnType)); + } + return desc.toString(); + } + + /** + * Get the descriptor of a particular type, as appropriate for either + * a parameter or return type in a method descriptor. + */ + private static String getTypeDescriptor(Class type) { + if (type.isPrimitive()) { + if (type == int.class) { + return "I"; + } else if (type == boolean.class) { + return "Z"; + } else if (type == byte.class) { + return "B"; + } else if (type == char.class) { + return "C"; + } else if (type == short.class) { + return "S"; + } else if (type == long.class) { + return "J"; + } else if (type == float.class) { + return "F"; + } else if (type == double.class) { + return "D"; + } else if (type == void.class) { + return "V"; + } else { + throw new Error("unrecognized primitive type: " + type); + } + } else if (type.isArray()) { + /* + * According to JLS 20.3.2, the getName() method on Class does + * return the VM type descriptor format for array classes (only); + * using that should be quicker than the otherwise obvious code: + * + * return "[" + getTypeDescriptor(type.getComponentType()); + */ + return type.getName().replace('.', '/'); + } else { + return "L" + type.getName().replace('.', '/') + ";"; + } + } + + /** + * Returns the binary name of the given type without package + * qualification. Nested types are treated no differently from + * top-level types, so for a nested type, the returned name will + * still be qualified with the simple name of its enclosing + * top-level type (and perhaps other enclosing types), the + * separator will be '$', etc. + **/ + public static String getUnqualifiedName(Class c) { + String binaryName = c.getName(); + return binaryName.substring(binaryName.lastIndexOf('.') + 1); + } +} diff --git a/src/share/classes/sun/rmi/server/WeakClassHashMap.java b/src/share/classes/sun/rmi/server/WeakClassHashMap.java new file mode 100644 index 000000000..e822c2655 --- /dev/null +++ b/src/share/classes/sun/rmi/server/WeakClassHashMap.java @@ -0,0 +1,88 @@ +/* + * Copyright 2003-2005 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ +package sun.rmi.server; + +import java.lang.ref.Reference; +import java.lang.ref.SoftReference; +import java.util.Map; +import java.util.WeakHashMap; + +/** + * Abstract class that maps Class objects to lazily-computed values of + * type V. A concrete subclass must implement the computeValue method + * to determine how the values are computed. + * + * The keys are only weakly reachable through this map, so this map + * does not prevent a class (along with its class loader, etc.) from + * being garbage collected if it is not otherwise strongly reachable. + * The values are only softly reachable through this map, so that the + * computed values generally persist while not otherwise strongly + * reachable, but their storage may be reclaimed if necessary. Also, + * note that if a key is strongly reachable from a value, then the key + * is effectively softly reachable through this map, which may delay + * garbage collection of classes (see 4429536). + **/ +public abstract class WeakClassHashMap<V> { + + private Map<Class<?>,ValueCell<V>> internalMap = + new WeakHashMap<Class<?>,ValueCell<V>>(); + + protected WeakClassHashMap() { } + + public V get(Class<?> remoteClass) { + /* + * Use a mutable cell (a one-element list) to hold the soft + * reference to a value, to allow the lazy value computation + * to be synchronized with entry-level granularity instead of + * by locking the whole table. + */ + ValueCell<V> valueCell; + synchronized (internalMap) { + valueCell = internalMap.get(remoteClass); + if (valueCell == null) { + valueCell = new ValueCell<V>(); + internalMap.put(remoteClass, valueCell); + } + } + synchronized (valueCell) { + V value = null; + if (valueCell.ref != null) { + value = (V) valueCell.ref.get(); + } + if (value == null) { + value = computeValue(remoteClass); + valueCell.ref = new SoftReference<V>(value); + } + return value; + } + } + + protected abstract V computeValue(Class<?> remoteClass); + + private static class ValueCell<T> { + Reference<T> ref = null; + ValueCell() { } + } +} diff --git a/src/share/classes/sun/rmi/server/resources/rmid.properties b/src/share/classes/sun/rmi/server/resources/rmid.properties new file mode 100644 index 000000000..51642b6e1 --- /dev/null +++ b/src/share/classes/sun/rmi/server/resources/rmid.properties @@ -0,0 +1,133 @@ +# +# +# Copyright 1998-2005 Sun Microsystems, Inc. All Rights Reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Sun designates this +# particular file as subject to the "Classpath" exception as provided +# by Sun in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, +# CA 95054 USA or visit www.sun.com if you need additional information or +# have any questions. +# + +# "rmid", inetd", and "wait" should not be translated. +rmid.syntax.exec.invalid=\ + rmid was launched from inetd with an invalid status (must be wait) + +# "rmid" and "inetd" should not be translated. +rmid.syntax.port.badarg=\ + port cannot be specified if rmid is launched from inetd + +# "port" here refers to a TCP port for the server to listen on. +rmid.syntax.port.badnumber=\ + port is not a number + +# "-port" should not be translated, because it's part of command syntax. +rmid.syntax.port.missing=\ + -port option requires argument + +# "-log" should not be translated, because it's part of command syntax. +rmid.syntax.log.missing=\ + -log option requires argument + +# "-log" should not be translated, because it's part of command syntax. +rmid.syntax.log.required=\ + -log option required + +# {0} = the (string) illegal argument in question +rmid.syntax.illegal.option=\ + illegal option: {0} + +# {0} = the (string) reason text that came with a thrown exception +# "Activation.main" should not be translated, because it's a codepoint +rmid.unexpected.exception=\ + Activation.main: an exception occurred: {0} + +# "java.home" should not be translated, because it's a property name +# "ActivatorImpl" should not be translated, because it's a codepoint +rmid.unfound.java.home.property=\ + ActivatorImpl: unable to locate java.home + +# "rmid" should not be translated +rmid.inherited.channel.info=\ + rmid startup with inherited channel + +# "Activation.main" should not be translated, because it's a codepoint +rmid.exec.policy.invalid=\ + Activation.main: invalid exec policy class + +# "rmid" should not be translated +rmid.exec.policy.exception=\ + rmid: attempt to obtain exec policy throws: + +# "rmid" should not be translated +rmid.exec.command=\ + rmid: debugExec: running "{0}" + +# "rmid" should not be translated +rmid.group.inactive=\ + rmid: activation group inactive: {0} + +# "Activation.main", "sun.rmi.activation.execPolicy", "ExecPermission" and +# "ExecOptionPermission" should not be translated, since they refer to +# class/permission names. +rmid.exec.perms.inadequate=\ + Activation.main: warning: sun.rmi.activation.execPolicy system\n\ + property unspecified and no ExecPermissions/ExecOptionPermissions\n\ + granted; subsequent activation attempts may fail due to unsuccessful\n\ + ExecPermission/ExecOptionPermission permission checks. For\n\ + documentation on how to configure rmid security, refer to:\n\ +\n\ + http://java.sun.com/j2se/1.4/docs/tooldocs/solaris/rmid.html\n\ + http://java.sun.com/j2se/1.4/docs/tooldocs/win32/rmid.html\n + +# "rmid", "-port", "-log", "-stop", "-C" and "-J" should not be translated, +# because they are syntax +rmid.usage=Usage: {0} <options>\ +\n\ +\nwhere <options> include:\ +\n -port <port> Specify port for rmid to use\ +\n -log <directory> Specify directory in which rmid writes log\ +\n -stop Stop current invocation of rmid (for specified port)\ +\n -C<runtime flag> Pass argument to each child process (activation group)\ +\n -J<runtime flag> Pass argument to the java interpreter\ +\n\ + +# This means "The currently running activation daemon has been shut down, +# and is about to exit". +rmid.daemon.shutdown=\ + activation daemon shut down + +# "rmid" should not be translated +rmid.restart.group.warning=\ +\nrmid: (WARNING) restart group throws: + +# "rmid" should not be translated +rmid.restart.service.warning=\ +\nrmid: (WARNING) restart service throws: + +# "rmid" should not be translated +rmid.log.update.warning=\ +\nrmid: (WARNING) log update throws: + +# "rmid" should not be translated +rmid.log.snapshot.warning=\ +\nrmid: (SEVERE) log snapshot throws: + +# "rmid" should not be translated +rmid.log.recover.warning=\ +\nrmid: (WARNING) {0}: skipping log record during recovery: diff --git a/src/share/classes/sun/rmi/server/resources/rmid_de.properties b/src/share/classes/sun/rmi/server/resources/rmid_de.properties new file mode 100644 index 000000000..62fd4906b --- /dev/null +++ b/src/share/classes/sun/rmi/server/resources/rmid_de.properties @@ -0,0 +1,133 @@ +# +# +# Copyright 2000-2005 Sun Microsystems, Inc. All Rights Reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Sun designates this +# particular file as subject to the "Classpath" exception as provided +# by Sun in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, +# CA 95054 USA or visit www.sun.com if you need additional information or +# have any questions. +# + +# "rmid", inetd", and "wait" should not be translated. +rmid.syntax.exec.invalid=\ + rmid wurde mit einem ung\u00fcltigen Status (muss wait sein) von inetd gestartet + +# "rmid" and "inetd" should not be translated. +rmid.syntax.port.badarg=\ + Der Port kann nicht angegeben werden, wenn rmid von inetd gestartet wird + +# "port" here refers to a TCP port for the server to listen on. +rmid.syntax.port.badnumber=\ + Port ist keine Zahl + +# "-port" should not be translated, because it's part of command syntax. +rmid.syntax.port.missing=\ + F\u00fcr Option -port ist ein Argument erforderlich. + +# "-log" should not be translated, because it's part of command syntax. +rmid.syntax.log.missing=\ + F\u00fcr Option -log ist ein Argument erforderlich. + +# "-log" should not be translated, because it's part of command syntax. +rmid.syntax.log.required=\ + Die Option -log ist erforderlich. + +# {0} = the (string) illegal argument in question +rmid.syntax.illegal.option=\ +Unzul\u00e4ssige Option: {0} + +# {0} = the (string) reason text that came with a thrown exception +# "Activation.main" should not be translated, because it's a codepoint +rmid.unexpected.exception=\ + Activation.main: Es ist eine Ausnahme aufgetreten: {0} + +# "java.home" should not be translated, because it's a property name +# "ActivatorImpl" should not be translated, because it's a codepoint +rmid.unfound.java.home.property=\ + ActivatorImpl: java.home konnte nicht gefunden werden. + +# "rmid" should not be translated +rmid.inherited.channel.info=\ + rmid-Start mit \u00fcbernommenem Kanal + +# "Activation.main" should not be translated, because it's a codepoint +rmid.exec.policy.invalid=\ + Activation.main: ung\u00fcltige exec-Verfahrensklasse + +# "rmid" should not be translated +rmid.exec.policy.exception=\ + rmid: Der Versuch, die exec-Richtlinie abzurufen, bewirkt: + +# "rmid" should not be translated +rmid.exec.command=\ + rmid: debugExec: "{0}" wird ausgef\u00fchrt + +# "rmid" should not be translated +rmid.group.inactive=\ + rmid: Aktivierungsgruppe inaktiv: {0} + +# "Activation.main", "sun.rmi.activation.execPolicy", "ExecPermission" and +# "ExecOptionPermission" should not be translated, since they refer to +# class/permission names. +rmid.exec.perms.inadequate=\ + Activation.main: Achtung: Systemeigenschaft sun.rmi.activation.execPolicy\n\ + nicht angegeben, und keine ExecPermissions/ExecOptionPermissions\n\ + gew\u00e4hrt; erneute Aktivierung kann wegen erfolgloser\n\ + Berechtigungspr\u00fcfungen ExecPermission/ExecOptionPermission fehlschlagen. Weitere\n\ + Dokumentation \u00fcber die Konfiguration von rmid-Sicherheit finden Sie unter:\n\ +\n\ + http://java.sun.com/j2se/1.4/docs/tooldocs/solaris/rmid.html\n\ + http://java.sun.com/j2se/1.4/docs/tooldocs/win32/rmid.html\n + +# "rmid", "-port", "-log", "-stop", "-C" and "-J" should not be translated, +# because they are syntax +rmid.usage=Syntax: {0} <Optionen>\ +\n\ +\nwobei folgende <Optionen> m\u00f6glich sind:\ +\n -port <Port> Angabe des Ports f\u00fcr rmid\ +\n -log <Verzeichnis> Angabe des Verzeichnisses, in das rmid die Log-Datei schreibt\ +\n -stop Aktuellen Aufruf von rmid stoppen (f\u00fcr den angegebenen Anschluss)\ +\n -C<Laufzeitflag> \u00dcbergeben des Arguments an jeden untergeordneten Prozess (Aktivierungsgruppe)\ +\n -J<Laufzeitflag> Argument an den Java-Interpreter \u00fcbergeben\ +\n\ + +# This means "The currently running activation daemon has been shut down, +# and is about to exit". +rmid.daemon.shutdown=\ +Aktivierungsdaemon wird geschlossen. + +# "rmid" should not be translated +rmid.restart.group.warning=\ +\nrmid: (WARNUNG) Neustart der Gruppe bewirkt: + +# "rmid" should not be translated +rmid.restart.service.warning=\ +\nrmid: (WARNUNG) Neustart des Dienstes bewirkt: + +# "rmid" should not be translated +rmid.log.update.warning=\ +\nrmid: (WARNUNG) Aktualisierung des Protokolls bewirkt: + +# "rmid" should not be translated +rmid.log.snapshot.warning=\ +\nrmid: (SCHWERW.) Momentaufnahme des Protokolls bewirkt: + +# "rmid" should not be translated +rmid.log.recover.warning=\ +\nrmid: (WARNUNG) {0}: \u00dcberspringen eines Protokolleintrags bei der Wiederherstellung: diff --git a/src/share/classes/sun/rmi/server/resources/rmid_es.properties b/src/share/classes/sun/rmi/server/resources/rmid_es.properties new file mode 100644 index 000000000..8fa7bfb91 --- /dev/null +++ b/src/share/classes/sun/rmi/server/resources/rmid_es.properties @@ -0,0 +1,133 @@ +# +# +# Copyright 2000-2005 Sun Microsystems, Inc. All Rights Reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Sun designates this +# particular file as subject to the "Classpath" exception as provided +# by Sun in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, +# CA 95054 USA or visit www.sun.com if you need additional information or +# have any questions. +# + +# "rmid", inetd", and "wait" should not be translated. +rmid.syntax.exec.invalid=\ + rmid se ha iniciado desde inetd con un estado no v\u00e1lido (debe ser wait) + +# "rmid" and "inetd" should not be translated. +rmid.syntax.port.badarg=\ + no se puede especificar el puerto si rmid se ha iniciado desde inetd + +# "port" here refers to a TCP port for the server to listen on. +rmid.syntax.port.badnumber=\ + el puerto no es un n\u00famero + +# "-port" should not be translated, because it's part of command syntax. +rmid.syntax.port.missing=\ + la opci\u00f3n -port requiere un argumento + +# "-log" should not be translated, because it's part of command syntax. +rmid.syntax.log.missing=\ + la opci\u00f3n -log requiere un argumento + +# "-log" should not be translated, because it's part of command syntax. +rmid.syntax.log.required=\ + la opci\u00f3n -log es obligatoria + +# {0} = the (string) illegal argument in question +rmid.syntax.illegal.option=\ +opci\u00f3n no permitida: {0} + +# {0} = the (string) reason text that came with a thrown exception +# "Activation.main" should not be translated, because it's a codepoint +rmid.unexpected.exception=\ + Activation.main: ha ocurrido una excepci\u00f3n: {0} + +# "java.home" should not be translated, because it's a property name +# "ActivatorImpl" should not be translated, because it's a codepoint +rmid.unfound.java.home.property=\ + ActivatorImpl: imposible encontrar java.home + +# "rmid" should not be translated +rmid.inherited.channel.info=\ + inicio de rmid con canal heredado + +# "Activation.main" should not be translated, because it's a codepoint +rmid.exec.policy.invalid=\ + Activation.main: Clase de norma de ejecuci\u00f3n no v\u00e1lida + +# "rmid" should not be translated +rmid.exec.policy.exception=\ + rmid: el intento de obtener la norma de ejecuci\u00f3n indica: + +# "rmid" should not be translated +rmid.exec.command=\ + rmid: debugExec: en ejecuci\u00f3n "{0}" + +# "rmid" should not be translated +rmid.group.inactive=\ + rmid: grupo de activaci\u00f3n inactivo: {0} + +# "Activation.main", "sun.rmi.activation.execPolicy", "ExecPermission" and +# "ExecOptionPermission" should not be translated, since they refer to +# class/permission names. +rmid.exec.perms.inadequate=\ + Activation.main: advertencia: no se han especificado las propiedades del sistema sun.rmi.activation.execPolicy\n\ + y no se han concedido ExecPermissions/ExecOptionPermissions;\n\ + los intentos de activaci\u00f3n posteriores pueden fallar debido a\n\ + comprobaciones de permiso ExecPermission/ExecOptionPermission no satisfactorias. Para\n\ + obtener documentaci\u00f3n sobre c\u00f3mo configurar la seguridad rmid, rem\u00edtase a:\n\ +\n\ + http://java.sun.com/j2se/1.4/docs/tooldocs/solaris/rmid.html\n\ + http://java.sun.com/j2se/1.4/docs/tooldocs/win32/rmid.html\n + +# "rmid", "-port", "-log", "-stop", "-C" and "-J" should not be translated, +# because they are syntax +rmid.usage=Sintaxis: {0} <opciones>\ +\n\ +\ndonde <opciones> incluye:\ +\n -port <puerto> Especificar puerto para uso de rmid\ +\n -log <directorio> Especificar directorio en el que rmid escribir\u00e1 el registro\ +\n -stop Detener la llamada actual de rmid (para el puerto especificado)\ +\n -C<indicador runtime> Pasar argumento a cada uno de los procesos subordinados (grupo de activaci\u00f3n)\ +\n -J<indicador runtime> Pasar argumento al int\u00e9rprete de Java\ +\n\ + +# This means "The currently running activation daemon has been shut down, +# and is about to exit". +rmid.daemon.shutdown=\ +daemon de activaci\u00f3n desactivado + +# "rmid" should not be translated +rmid.restart.group.warning=\ +\nrmid: (ADVERTENCIA) el reinicio del grupo indica: + +# "rmid" should not be translated +rmid.restart.service.warning=\ +\nrmid: (ADVERTENCIA) el reinicio del servicio indica: + +# "rmid" should not be translated +rmid.log.update.warning=\ +\nrmid: (ADVERTENCIA) la actualizaci\u00f3n del registro indica: + +# "rmid" should not be translated +rmid.log.snapshot.warning=\ +\nrmid: (GRAVE) la instant\u00e1nea del registro indica: + +# "rmid" should not be translated +rmid.log.recover.warning=\ +\nrmid: (ADVERTENCIA) {0}: omitiendo la escritura del registro durante la recuperaci\u00f3n: diff --git a/src/share/classes/sun/rmi/server/resources/rmid_fr.properties b/src/share/classes/sun/rmi/server/resources/rmid_fr.properties new file mode 100644 index 000000000..c8c0b1ecf --- /dev/null +++ b/src/share/classes/sun/rmi/server/resources/rmid_fr.properties @@ -0,0 +1,133 @@ +# +# +# Copyright 2000-2005 Sun Microsystems, Inc. All Rights Reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Sun designates this +# particular file as subject to the "Classpath" exception as provided +# by Sun in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, +# CA 95054 USA or visit www.sun.com if you need additional information or +# have any questions. +# + +# "rmid", inetd", and "wait" should not be translated. +rmid.syntax.exec.invalid=\ + rmid a \u00e9t\u00e9 lanc\u00e9 depuis inetd avec un statut invalide (doit \u00eatre wait) + +# "rmid" and "inetd" should not be translated. +rmid.syntax.port.badarg=\ + impossible de sp\u00e9cifier port si rmid est lanc\u00e9 depuis inetd + +# "port" here refers to a TCP port for the server to listen on. +rmid.syntax.port.badnumber=\ + port n'est pas un num\u00e9ro + +# "-port" should not be translated, because it's part of command syntax. +rmid.syntax.port.missing=\ + l'option -port exige un argument + +# "-log" should not be translated, because it's part of command syntax. +rmid.syntax.log.missing=\ + l'option -log exige un argument + +# "-log" should not be translated, because it's part of command syntax. +rmid.syntax.log.required=\ + option -log requise + +# {0} = the (string) illegal argument in question +rmid.syntax.illegal.option=\ + option incorrecte : {0} + +# {0} = the (string) reason text that came with a thrown exception +# "Activation.main" should not be translated, because it's a codepoint +rmid.unexpected.exception=\ + Activation.main : une exception s''est produite : {0} + +# "java.home" should not be translated, because it's a property name +# "ActivatorImpl" should not be translated, because it's a codepoint +rmid.unfound.java.home.property=\ + ActivatorImpl : impossible de localiser java.home + +# "rmid" should not be translated +rmid.inherited.channel.info=\ + d\u00e9marrage de rmid avec le canal existant + +# "Activation.main" should not be translated, because it's a codepoint +rmid.exec.policy.invalid=\ + Activation.main : classe de r\u00e8gle exec incorrecte + +# "rmid" should not be translated +rmid.exec.policy.exception=\ + rmid: tenter d'obtenir des basculements de classes exec : + +# "rmid" should not be translated +rmid.exec.command=\ + rmid: debugExec : ex\u00e9cution de "{0}" + +# "rmid" should not be translated +rmid.group.inactive=\ + rmid : groupe d''activation inactif : {0} + +# "Activation.main", "sun.rmi.activation.execPolicy", "ExecPermission" and +# "ExecOptionPermission" should not be translated, since they refer to +# class/permission names. +rmid.exec.perms.inadequate=\ + Activation.main: avertissement : syst\u00e8me sun.rmi.activation.execPolicy\n\ + propri\u00e9t\u00e9 non sp\u00e9cifi\u00e9e et ExecPermissions/ExecOptionPermissions\n\ + non autoris\u00e9s ; les tentatives d'activation suivantes risquent d'\u00e9chouer en raison de la v\u00e9rification des permissions\n\ + ExecPermission/ExecOptionPermission. Pour obtenir de la\n\ + documentation sur la configuration de la s\u00e9curit\u00e9 rmid, reportez-vous \u00e0 :\n\ +\n\ + http://java.sun.com/j2se/1.4/docs/tooldocs/solaris/rmid.html\n\ + http://javasun.com/j2se/1.4/docs/tooldocs/win32/rmid.html\n + +# "rmid", "-port", "-log", "-stop", "-C" and "-J" should not be translated, +# because they are syntax +rmid.usage=Syntaxe : {0} <options>\ +\n\ +\no\u00f9 <options> comprend :\ +\n -port <port> Port que rmid doit utiliser\ +\n -log <directory> R\u00e9pertoire o\u00f9 rmid enregistre le journal\ +\n -stop Arr\u00eater l''appel courant de rmid (pour le port sp\u00e9cifi\u00e9)\ +\n -C<indicateur d''ex\u00e9cution> Passe l''argument \u00e0 chaque processus fils (groupe d''activation)\ +\n -J<indicateur d''ex\u00e9cution> Passe l''argument \u00e0 l''interpr\u00e9teur Java\ +\n\ + +# This means "The currently running activation daemon has been shut down, +# and is about to exit". +rmid.daemon.shutdown=\ + d\u00e9mon d'activation arr\u00eat\u00e9 + +# "rmid" should not be translated +rmid.restart.group.warning=\ +\nrmid : (AVERTISSEMENT) red\u00e9marrer les basculements de groupes : + +# "rmid" should not be translated +rmid.restart.service.warning=\ +\nrmid : (AVERTISSEMENT) red\u00e9marrer les basculements de services : + +# "rmid" should not be translated +rmid.log.update.warning=\ +\nrmid : (AVERTISSEMENT) consigner les basculements de mises \u00e0 jour : + +# "rmid" should not be translated +rmid.log.snapshot.warning=\ +\nrmid : (GRAVE) consigner les basculements de captures instantan\u00e9es : + +# "rmid" should not be translated +rmid.log.recover.warning=\ +\nrmid : (AVERTISSEMENT) {0} : enregistrement du journal ignor\u00e9 pendant la r\u00e9cup\u00e9ration : diff --git a/src/share/classes/sun/rmi/server/resources/rmid_it.properties b/src/share/classes/sun/rmi/server/resources/rmid_it.properties new file mode 100644 index 000000000..0a47e055b --- /dev/null +++ b/src/share/classes/sun/rmi/server/resources/rmid_it.properties @@ -0,0 +1,133 @@ +# +# +# Copyright 2000-2005 Sun Microsystems, Inc. All Rights Reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Sun designates this +# particular file as subject to the "Classpath" exception as provided +# by Sun in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, +# CA 95054 USA or visit www.sun.com if you need additional information or +# have any questions. +# + +# "rmid", inetd", and "wait" should not be translated. +rmid.syntax.exec.invalid=\ + rmid \u00e8 stato avviato da inetd con uno stato non valido (diverso da wait) + +# "rmid" and "inetd" should not be translated. +rmid.syntax.port.badarg=\ + non \u00e8 possibile specificare la porta se rmid viene avviato da inetd + +# "port" here refers to a TCP port for the server to listen on. +rmid.syntax.port.badnumber=\ + la porta non \u00e8 un numero + +# "-port" should not be translated, because it's part of command syntax. +rmid.syntax.port.missing=\ + L'opzione -port richiede un argomento + +# "-log" should not be translated, because it's part of command syntax. +rmid.syntax.log.missing=\ + L'opzione -log richiede un argomento + +# "-log" should not be translated, because it's part of command syntax. +rmid.syntax.log.required=\ + \u00c8 richiesta l'opzione -log + +# {0} = the (string) illegal argument in question +rmid.syntax.illegal.option=\ + opzione illegale: {0} + +# {0} = the (string) reason text that came with a thrown exception +# "Activation.main" should not be translated, because it's a codepoint +rmid.unexpected.exception=\ + Activation.main: si \u00e8 verificata un''eccezione: {0} + +# "java.home" should not be translated, because it's a property name +# "ActivatorImpl" should not be translated, because it's a codepoint +rmid.unfound.java.home.property=\ + ActivatorImpl: impossibile individuare java.home + +# "rmid" should not be translated +rmid.inherited.channel.info=\ + Avvio di rmid con canale ereditato + +# "Activation.main" should not be translated, because it's a codepoint +rmid.exec.policy.invalid=\ + Activation.main: classe di policy eseguibile non valida + +# "rmid" should not be translated +rmid.exec.policy.exception=\ + rmid: il tentativo di ottenere i criteri di esecuzione ha restituito: + +# "rmid" should not be translated +rmid.exec.command=\ + rmid: debugExec: esecuzione di "{0}" in corso + +# "rmid" should not be translated +rmid.group.inactive=\ + rmid: gruppo attivazione inattivo: {0} + +# "Activation.main", "sun.rmi.activation.execPolicy", "ExecPermission" and +# "ExecOptionPermission" should not be translated, since they refer to +# class/permission names. +rmid.exec.perms.inadequate=\ + Activation.main: avviso: sistema sun.rmi.activation.execPolicy\n\ + Propriet\u00e0 non specificata e nessun ExecPermissions/ExecOptionPermissions\n\ + garantito. I tentativi di attivazione successivi potrebbero fallire a causa di \n\ + controlli di autorizzazione ExecPermission/ExecOptionPermission non andati a buon fine. Per\n\ + la documentazione e le modalit\u00e0 di configurazione della protezione rmid, fare riferimento a (informazioni in inglese):\n\ +\n\ + http://java.sun.com/j2se/1.4/docs/tooldocs/solaris/rmid.html\n\ + http://java.sun.com/j2se/1.4/docs/tooldocs/win32/rmid.html\n + +# "rmid", "-port", "-log", "-stop", "-C" and "-J" should not be translated, +# because they are syntax +rmid.usage=Utilizzo: {0} <opzioni>\ +\n\ +\ndove <opzioni> comprende:\ +\n -port <porta> Specifica la porta usata da rmid\ +\n -log <directory> Specifica la directory in cui rmid scrive il log\ +\n -stop Interrompe l''invocazione corrente di rmid (per la porta specificata)\ +\n -C<flag runtime> Passa l''argomento a ciascun processo figlio (gruppo di attivazione)\ +\n -J<flag runtime> Passa l''argomento all''interprete java\ +\n\ + +# This means "The currently running activation daemon has been shut down, +# and is about to exit". +rmid.daemon.shutdown=\ + daemon di attivazione terminato + +# "rmid" should not be translated +rmid.restart.group.warning=\ +\nrmid: (AVVERTENZA) il riavvio del gruppo ha restituito: + +# "rmid" should not be translated +rmid.restart.service.warning=\ +\nrmid: (AVVERTENZA) il riavvio del servizio ha restituito: + +# "rmid" should not be translated +rmid.log.update.warning=\ +\nrmid: (AVVERTENZA) il log dell'aggiornamento ha restituito: + +# "rmid" should not be translated +rmid.log.snapshot.warning=\ +\nrmid: (GRAVE) il log dell'istantanea ha restituito: + +# "rmid" should not be translated +rmid.log.recover.warning=\ +\nrmid: (AVVERTENZA) {0}: record del log ignorato durante il ripristino: diff --git a/src/share/classes/sun/rmi/server/resources/rmid_ja.properties b/src/share/classes/sun/rmi/server/resources/rmid_ja.properties new file mode 100644 index 000000000..6bc0e917e --- /dev/null +++ b/src/share/classes/sun/rmi/server/resources/rmid_ja.properties @@ -0,0 +1,134 @@ +# +# +# Copyright 1999-2005 Sun Microsystems, Inc. All Rights Reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Sun designates this +# particular file as subject to the "Classpath" exception as provided +# by Sun in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, +# CA 95054 USA or visit www.sun.com if you need additional information or +# have any questions. +# + +# "rmid", inetd", and "wait" should not be translated. +rmid.syntax.exec.invalid=\ + rmid \u304c inetd \u304b\u3089\u7121\u52b9\u306a\u72b6\u614b\u3067\u8d77\u52d5\u3055\u308c\u307e\u3057\u305f (wait \u3067\u306a\u3051\u308c\u3070\u306a\u308a\u307e\u305b\u3093)\u3002 + +# "rmid" and "inetd" should not be translated. +rmid.syntax.port.badarg=\ + rmid \u304c inetd \u304b\u3089\u8d77\u52d5\u3055\u308c\u305f\u5834\u5408\u3001\u30dd\u30fc\u30c8\u306f\u6307\u5b9a\u3067\u304d\u307e\u305b\u3093\u3002 + +# "port" here refers to a TCP port for the server to listen on. +rmid.syntax.port.badnumber=\ + "port" \u306f\u756a\u53f7\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002 + +# "-port" should not be translated, because it's part of command syntax. +rmid.syntax.port.missing=\ + -port \u30aa\u30d7\u30b7\u30e7\u30f3\u306b\u306f\u5f15\u6570\u304c\u5fc5\u8981\u3067\u3059 + +# "-log" should not be translated, because it's part of command syntax. +rmid.syntax.log.missing=\ + -log \u30aa\u30d7\u30b7\u30e7\u30f3\u306b\u306f\u5f15\u6570\u304c\u5fc5\u8981\u3067\u3059 + +# "-log" should not be translated, because it's part of command syntax. +rmid.syntax.log.required=\ + -log \u30aa\u30d7\u30b7\u30e7\u30f3\u304c\u5fc5\u8981\u3067\u3059 + +# {0} = the (string) illegal argument in question +rmid.syntax.illegal.option=\ + \u4e0d\u6b63\u306a\u30aa\u30d7\u30b7\u30e7\u30f3: {0} + +# {0} = the (string) reason text that came with a thrown exception +# "Activation.main" should not be translated, because it's a codepoint +rmid.unexpected.exception=\ + Activation.main: \u4f8b\u5916\u304c\u767a\u751f\u3057\u307e\u3057\u305f: {0} + +# "java.home" should not be translated, because it's a property name +# "ActivatorImpl" should not be translated, because it's a codepoint +rmid.unfound.java.home.property=\ + ActivatorImpl: java.home \u3092\u691c\u51fa\u3067\u304d\u307e\u305b\u3093\u3002 + +# "rmid" should not be translated +rmid.inherited.channel.info=\ + rmid \u306f\u7d99\u627f\u3055\u308c\u305f\u30c1\u30e3\u30cd\u30eb\u3067\u8d77\u52d5\u3057\u307e\u3059\u3002 + +# "Activation.main" should not be translated, because it's a codepoint +rmid.exec.policy.invalid=\ + Activation.main: \u7121\u52b9\u306a\u5b9f\u884c\u30dd\u30ea\u30b7\u30fc\u30af\u30e9\u30b9\u3067\u3059\u3002 + +# "rmid" should not be translated +rmid.exec.policy.exception=\ + rmid: \u5b9f\u884c\u30dd\u30ea\u30b7\u30fc\u306e\u53d6\u5f97\u6642\u306e\u4f8b\u5916: + +# "rmid" should not be translated +rmid.exec.command=\ + rmid: debugExec: "{0}" \u3092\u5b9f\u884c\u4e2d + +# "rmid" should not be translated +rmid.group.inactive=\ + rmid: \u8d77\u52d5\u30b0\u30eb\u30fc\u30d7\u304c\u505c\u6b62\u3057\u3066\u3044\u307e\u3059: {0} + +# "Activation.main", "sun.rmi.activation.execPolicy", "ExecPermission" and +# "ExecOptionPermission" should not be translated, since they refer to +# class/permission names. +rmid.exec.perms.inadequate=\ + Activation.main: \u8b66\u544a:sun.rmi.activation.execPolicy \u30b7\u30b9\u30c6\u30e0\n\ + \u30d7\u30ed\u30d1\u30c6\u30a3\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u306a\u3044\u3001\u307e\u305f\u306f \n\ + ExecPermissions/ExecOptionPermissions \u304c\u8a31\u53ef\u3055\u308c\u307e\u305b\u3093\u3002\n\ + ExecPermissions/ExecOptionPermissions \u30a2\u30af\u30bb\u30b9\u6a29\u691c\u67fb\u3067\u8a31\u53ef\u3055\u308c\n\ + \u306a\u3044\u305f\u3081\u3001\u3042\u3068\u306b\u7d9a\u304f\u8d77\u52d5\u306f\u5931\u6557\u3059\u308b\u53ef\u80fd\u6027\u304c\u3042\u308a\u307e\u3059\u3002\n\ + rmid \u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u306e\u8a2d\u5b9a\u65b9\u6cd5\u306b\u3064\u3044\u3066\u306f\u3001\u6b21\u306e\u30de\u30cb\u30e5\u30a2\u30eb\u3092\u53c2\u7167\u3057\u3066\n\ + \u304f\u3060\u3055\u3044\u3002:\n\ +\n\ + http://java.sun.com/j2se/1.4/docs/tooldocs/solaris/rmid.html\n\ + http://java.sun.com/j2se/1.4/docs/tooldocs/win32/rmid.html\n + +# "rmid", "-port", "-log", "-stop", "-C" and "-J" should not be translated, +# because they are syntax +rmid.usage=\u4f7f\u3044\u65b9: {0} <options>\ +\n\ +\n<options> \u306b\u306f\u6b21\u306e\u3082\u306e\u304c\u3042\u308a\u307e\u3059\u3002\ +\n -port <port> rmid \u304c\u4f7f\u7528\u3059\u308b\u30dd\u30fc\u30c8\u3092\u6307\u5b9a\u3059\u308b\ +\n -log <directory> rmid \u304c\u30ed\u30b0\u3092\u66f8\u304d\u8fbc\u3080\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u3092\u6307\u5b9a\u3059\u308b\ +\n -stop \u6307\u5b9a\u30dd\u30fc\u30c8\u306b\u5bfe\u3059\u308b rmid \u306e\u73fe\u5728\u306e\u547c\u3073\u51fa\u3057\u3092\u4e2d\u6b62\u3059\u308b\ +\n -C<runtime flag> \u5404\u5b50\u30d7\u30ed\u30bb\u30b9 (\u8d77\u52d5\u30b0\u30eb\u30fc\u30d7) \u306b\u5f15\u6570\u3092\u6e21\u3059\ +\n -J<runtime flag> java \u30a4\u30f3\u30bf\u30d7\u30ea\u30bf\u306b\u5f15\u6570\u3092\u6e21\u3059\ +\n +# This means "The currently running activation daemon has been shut down, +# and is about to exit". +rmid.daemon.shutdown=\ + \u8d77\u52d5\u30c7\u30fc\u30e2\u30f3\u304c\u505c\u6b62\u3057\u307e\u3057\u305f\u3002 + +# "rmid" should not be translated +rmid.restart.group.warning=\ +\nrmid: (\u8b66\u544a) \u30b0\u30eb\u30fc\u30d7\u306e\u518d\u8d77\u52d5\u6642\u306e\u4f8b\u5916: + +# "rmid" should not be translated +rmid.restart.service.warning=\ +\nrmid: (\u8b66\u544a) \u30b5\u30fc\u30d3\u30b9\u306e\u518d\u8d77\u52d5\u6642\u306e\u4f8b\u5916: + +# "rmid" should not be translated +rmid.log.update.warning=\ +\nrmid: (\u8b66\u544a) \u30ed\u30b0\u66f4\u65b0\u6642\u306e\u4f8b\u5916: + +# "rmid" should not be translated +rmid.log.snapshot.warning=\ +\nrmid: (\u91cd\u5927) \u30ed\u30b0\u30b9\u30ca\u30c3\u30d7\u30b7\u30e7\u30c3\u30c8\u6642\u306e\u4f8b\u5916: + +# "rmid" should not be translated +rmid.log.recover.warning=\ +\nrmid: (\u8b66\u544a) {0}: \u56de\u5fa9\u4e2d\u306e\u30ed\u30b0\u30ec\u30b3\u30fc\u30c9\u306e\u30b9\u30ad\u30c3\u30d7: diff --git a/src/share/classes/sun/rmi/server/resources/rmid_ko.properties b/src/share/classes/sun/rmi/server/resources/rmid_ko.properties new file mode 100644 index 000000000..73e02f75f --- /dev/null +++ b/src/share/classes/sun/rmi/server/resources/rmid_ko.properties @@ -0,0 +1,133 @@ +# +# +# Copyright 2000-2005 Sun Microsystems, Inc. All Rights Reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Sun designates this +# particular file as subject to the "Classpath" exception as provided +# by Sun in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, +# CA 95054 USA or visit www.sun.com if you need additional information or +# have any questions. +# + +# "rmid", inetd", and "wait" should not be translated. +rmid.syntax.exec.invalid=\ + rmid\uac00 \uc798\ubabb\ub41c \uc0c1\ud0dc\ub97c \uac00\uc9c4 inetd\uc5d0\uc11c \uc2dc\uc791\ub418\uc5c8\uc2b5\ub2c8\ub2e4. (wait\uc774\uc5b4\uc57c \ud568) + +# "rmid" and "inetd" should not be translated. +rmid.syntax.port.badarg=\ + rmid\uac00 inetd\uc5d0\uc11c \uc2dc\uc791\ub41c \uacbd\uc6b0 \ud3ec\ud2b8\ub97c \uc9c0\uc815\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. + +# "port" here refers to a TCP port for the server to listen on. +rmid.syntax.port.badnumber=\ + \ud3ec\ud2b8\ub294 \uc22b\uc790\uac00 \uc544\ub2d9\ub2c8\ub2e4. + +# "-port" should not be translated, because it's part of command syntax. +rmid.syntax.port.missing=\ + -port \uc635\uc158\uc744 \uc0ac\uc6a9\ud558\ub824\uba74 \uc778\uc790\uac00 \ud544\uc694\ud569\ub2c8\ub2e4. + +# "-log" should not be translated, because it's part of command syntax. +rmid.syntax.log.missing=\ + -log \uc635\uc158\uc744 \uc0ac\uc6a9\ud558\ub824\uba74 \uc778\uc790\uac00 \ud544\uc694\ud569\ub2c8\ub2e4. + +# "-log" should not be translated, because it's part of command syntax. +rmid.syntax.log.required=\ + -log \uc635\uc158\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. + +# {0} = the (string) illegal argument in question +rmid.syntax.illegal.option=\ + \uc798\ubabb\ub41c \uc635\uc158: {0} + +# {0} = the (string) reason text that came with a thrown exception +# "Activation.main" should not be translated, because it's a codepoint +rmid.unexpected.exception=\ + Activation.main: \uc608\uc678 \ubc1c\uc0dd: {0} + +# "java.home" should not be translated, because it's a property name +# "ActivatorImpl" should not be translated, because it's a codepoint +rmid.unfound.java.home.property=\ + ActivatorImpl: java.home\uc744 \ucc3e\uc744 \uc218 \uc5c6\uc74c + +# "rmid" should not be translated +rmid.inherited.channel.info=\ + \uc0c1\uc18d \ucc44\ub110\uc744 \uc0ac\uc6a9\ud558\uc5ec rmid \uc2dc\uc791 + +# "Activation.main" should not be translated, because it's a codepoint +rmid.exec.policy.invalid=\ + Activation.main: \uc798\ubabb\ub41c \uc2e4\ud589 \uc815\ucc45 \ud074\ub798\uc2a4 + +# "rmid" should not be translated +rmid.exec.policy.exception=\ + rmid: \uc2e4\ud589 \uc815\ucc45 \uac00\uc838\uc624\uae30 \uc2dc\ub3c4\ub85c \uc778\ud574 \ub2e4\uc74c \uc624\ub958\uac00 \ubc1c\uc0dd\ud568: + +# "rmid" should not be translated +rmid.exec.command=\ + rmid: debugExec: "{0}" \uc2e4\ud589 \uc911 + +# "rmid" should not be translated +rmid.group.inactive=\ + rmid: \ud65c\uc131\ud654 \uadf8\ub8f9 \ube44\ud65c\uc131: {0} + +# "Activation.main", "sun.rmi.activation.execPolicy", "ExecPermission" and +# "ExecOptionPermission" should not be translated, since they refer to +# class/permission names. +rmid.exec.perms.inadequate=\ + Activation.main: \uacbd\uace0: sun.rmi.activation.execPolicy \uc2dc\uc2a4\ud15c \ub4f1\ub85d \uc815\ubcf4\uac00\n\ + \uc9c0\uc815\ub418\uc9c0 \uc54a\uc558\uace0 ExecPermissions/ExecOptionPermissions\uc774 \ubd80\uc5ec\ub418\uc9c0\n\ + \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uacc4\uc18d\ub418\ub294 \ud65c\uc131\ud654 \uc2dc\ub3c4\ub294 ExecPermission/ExecOptionPermission\uc758\n\ + \ud5c8\uac00 \ud655\uc778 \uc2e4\ud328\ub85c \uc778\ud574 \uc2e4\ud328\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.\n\ + rmid \ubcf4\uc548\uc744 \uad6c\uc131\ud558\ub294 \ubc29\ubc95\uc5d0 \ub300\ud55c \uc124\uba85\uc11c\ub294 \ub2e4\uc74c \uc6f9 \uc0ac\uc774\ud2b8\ub97c \ucc38\uc870\ud558\uc2ed\uc2dc\uc624.\n\ +\n\ +http://java.sun.com/j2se/1.4/docs/tooldocs/solaris/rmid.html\n\ +http://java.sun.com/j2se/1.4/docs/tooldocs/win32/rmid.html\n + +# "rmid", "-port", "-log", "-stop", "-C" and "-J" should not be translated, +# because they are syntax +rmid.usage=\uc0ac\uc6a9\ubc95: {0} <\uc635\uc158> \ +\n\ +\n<\uc635\uc158>\uc740 \ub2e4\uc74c\uc744 \ud3ec\ud568\ud569\ub2c8\ub2e4:\ +\n -port <port> rmid\uac00 \uc0ac\uc6a9\ud560 \ud3ec\ud2b8 \uc9c0\uc815\ +\n -log <directory> rmid\uac00 \ub85c\uadf8\ub97c \uae30\ub85d\ud560 \ub514\ub809\ud1a0\ub9ac \uc9c0\uc815\ +\n -stop rmid\uc758 \ud604\uc7ac \ud638\ucd9c \uc911\ub2e8 (\uc9c0\uc815\ub41c \ud3ec\ud2b8\uc5d0 \ub300\ud574)\ +\n -C<runtime flag> \uac01\uac01\uc758 \uc790\uc2dd \ud504\ub85c\uc138\uc2a4\uc5d0 \uc778\uc790 \uc804\ub2ec (\ud65c\uc131\ud654 \uadf8\ub8f9)\ +\n -J<runtime flag> Java \uc778\ud130\ud504\ub9ac\ud130\uc5d0 \uc778\uc790\ub97c \uc804\ub2ec\ud569\ub2c8\ub2e4.\ +\n\ + +# This means "The currently running activation daemon has been shut down, +# and is about to exit". +rmid.daemon.shutdown=\ + \ud65c\uc131 \ub370\ubaac \uc167\ub2e4\uc6b4 + +# "rmid" should not be translated +rmid.restart.group.warning=\ +\nrmid: (\uacbd\uace0) \uadf8\ub8f9 \ub2e4\uc2dc \uc2dc\uc791\uc73c\ub85c \uc778\ud574 \ub2e4\uc74c \uc624\ub958\uac00 \ubc1c\uc0dd\ud568: + +# "rmid" should not be translated +rmid.restart.service.warning=\ +\nrmid: (\uacbd\uace0) \uc11c\ube44\uc2a4 \ub2e4\uc2dc \uc2dc\uc791\uc73c\ub85c \uc778\ud574 \ub2e4\uc74c \uc624\ub958\uac00 \ubc1c\uc0dd\ud568: + +# "rmid" should not be translated +rmid.log.update.warning=\ +\nrmid: (\uacbd\uace0) \ub85c\uadf8 \uc5c5\ub370\uc774\ud2b8\ub85c \uc778\ud574 \ub2e4\uc74c \uc624\ub958\uac00 \ubc1c\uc0dd\ud568: + +# "rmid" should not be translated +rmid.log.snapshot.warning=\ +\nrmid: (\uc2ec\uac01) \ub85c\uadf8 \uc2a4\ub0c5\uc0f7\uc73c\ub85c \uc778\ud574 \ub2e4\uc74c \uc624\ub958\uac00 \ubc1c\uc0dd\ud568: + +# "rmid" should not be translated +rmid.log.recover.warning=\ +\nrmid: (\uacbd\uace0) {0}: \ubcf5\uad6c\ud558\ub294 \ub3d9\uc548 \ub808\ucf54\ub4dc \uae30\ub85d \uac74\ub108\ub6f0\uae30: diff --git a/src/share/classes/sun/rmi/server/resources/rmid_sv.properties b/src/share/classes/sun/rmi/server/resources/rmid_sv.properties new file mode 100644 index 000000000..2eded0915 --- /dev/null +++ b/src/share/classes/sun/rmi/server/resources/rmid_sv.properties @@ -0,0 +1,133 @@ +# +# +# Copyright 2000-2005 Sun Microsystems, Inc. All Rights Reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Sun designates this +# particular file as subject to the "Classpath" exception as provided +# by Sun in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, +# CA 95054 USA or visit www.sun.com if you need additional information or +# have any questions. +# + +# "rmid", inetd", and "wait" should not be translated. +rmid.syntax.exec.invalid=\ + rmid startades fr\u00e5n inetd med ogiltig status (m\u00e5ste vara wait) + +# "rmid" and "inetd" should not be translated. +rmid.syntax.port.badarg=\ + port kan inte anges om rmid startas fr\u00e5n inetd + +# "port" here refers to a TCP port for the server to listen on. +rmid.syntax.port.badnumber=\ + port \u00e4r inte ett nummer + +# "-port" should not be translated, because it's part of command syntax. +rmid.syntax.port.missing=\ + alternativet -port kr\u00e4ver argument + +# "-log" should not be translated, because it's part of command syntax. +rmid.syntax.log.missing=\ + alternativet -log kr\u00e4ver argument + +# "-log" should not be translated, because it's part of command syntax. +rmid.syntax.log.required=\ + alternativet -log kr\u00e4vs + +# {0} = the (string) illegal argument in question +rmid.syntax.illegal.option=\ +ogiltigt alternativ: {0} + +# {0} = the (string) reason text that came with a thrown exception +# "Activation.main" should not be translated, because it's a codepoint +rmid.unexpected.exception=\ + Activation.main: ett undantagsavbrott har intr\u00e4ffat: {0} + +# "java.home" should not be translated, because it's a property name +# "ActivatorImpl" should not be translated, because it's a codepoint +rmid.unfound.java.home.property=\ + ActivatorImpl: det g\u00e5r inte att hitta java.home + +# "rmid" should not be translated +rmid.inherited.channel.info=\ + uppstart av rmid med \u00e4rvd kanal + +# "Activation.main" should not be translated, because it's a codepoint +rmid.exec.policy.invalid=\ + Activation.main: ogiltig exec policy-klass + +# "rmid" should not be translated +rmid.exec.policy.exception=\ + rmid: f\u00f6rs\u00f6k att h\u00e4mta throws f\u00f6r exec-policy: + +# "rmid" should not be translated +rmid.exec.command=\ + rmid: debugExec: k\u00f6r "{0}" + +# "rmid" should not be translated +rmid.group.inactive=\ + rmid: aktiveringsgruppen \u00e4r inaktiv: {0} + +# "Activation.main", "sun.rmi.activation.execPolicy", "ExecPermission" and +# "ExecOptionPermission" should not be translated, since they refer to +# class/permission names. +rmid.exec.perms.inadequate=\ + Activation.main: varning: systemegenskapen sun.rmi.activation.execPolicy\n\ + \u00e4r inte specificerad och inga ExecPermissions/ExecOptionPermissions\n\ + har utdelats. Efterf\u00f6ljande aktiveringsf\u00f6rs\u00f6k kan misslyckas p\u00e5 grund av misslyckade\n\ + kontroller av ExecPermission/ExecOptionPermission-beh\u00f6righet. Mer\n\ + information om hur du konfigurerar rmid-s\u00e4kerhet finns i:\n\ +\n\ + http://java.sun.com/j2se/1.4/docs/tooldocs/solaris/rmid.html\n\ + http://java.sun.com/j2se/1.4/docs/tooldocs/win32/rmid.html\n + +# "rmid", "-port", "-log", "-stop", "-C" and "-J" should not be translated, +# because they are syntax +rmid.usage=G\u00f6r s\u00e5 h\u00e4r: {0} <alternativ>\ +\n\ +\nd\u00e4r <alternativ> omfattar:\ +\n -port <port> Ange porten f\u00f6r rmid\ +\n -log <katalog> Ange katalogen d\u00e4r rmid ska spara loggen\ +\n -stop Stoppa p\u00e5g\u00e5ende rmid-anrop (f\u00f6r angiven port)\ +\n -C<k\u00f6rtidsflagga> Skicka argumentet till varje underordnad process (aktiveringsgrupp)\ +\n -J<k\u00f6rtidsflagga> Skicka argumentet till java-tolken\ +\n\ + +# This means "The currently running activation daemon has been shut down, +# and is about to exit". +rmid.daemon.shutdown=\ + aktiveringsdemonen avslutas + +# "rmid" should not be translated +rmid.restart.group.warning=\ +\nrmid: (VARNING) starta om grupp-throws: + +# "rmid" should not be translated +rmid.restart.service.warning=\ +\nrmid: (VARNING) starta om service-throws: + +# "rmid" should not be translated +rmid.log.update.warning=\ +\nrmid: (VARNING) logga uppdaterings-throws: + +# "rmid" should not be translated +rmid.log.snapshot.warning=\ +\nrmid: (ALLVARLIGT) logga \u00f6gonblicks-throws: + +# "rmid" should not be translated +rmid.log.recover.warning=\ +\nrmid: (VARNING) {0}: hoppa \u00f6ver loggpost under \u00e5terst\u00e4llning: diff --git a/src/share/classes/sun/rmi/server/resources/rmid_zh_CN.properties b/src/share/classes/sun/rmi/server/resources/rmid_zh_CN.properties new file mode 100644 index 000000000..80476ef2b --- /dev/null +++ b/src/share/classes/sun/rmi/server/resources/rmid_zh_CN.properties @@ -0,0 +1,133 @@ +# +# +# Copyright 1999-2005 Sun Microsystems, Inc. All Rights Reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Sun designates this +# particular file as subject to the "Classpath" exception as provided +# by Sun in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, +# CA 95054 USA or visit www.sun.com if you need additional information or +# have any questions. +# + +# "rmid", inetd", and "wait" should not be translated. +rmid.syntax.exec.invalid=\ + rmid \u5df2\u4ece inetd \u542f\u52a8\uff0c\u4e14\u72b6\u6001\u65e0\u6548 (\u5fc5\u987b\u7b49\u5f85) + +# "rmid" and "inetd" should not be translated. +rmid.syntax.port.badarg=\ + \u5982\u679c rmid \u4ece inetd \u542f\u52a8\uff0c\u5219\u65e0\u6cd5\u6307\u5b9a\u7aef\u53e3 + +# "port" here refers to a TCP port for the server to listen on. +rmid.syntax.port.badnumber=\ + \u7aef\u53e3\u4e0d\u662f\u4e00\u4e2a\u6570\u5b57 + +# "-port" should not be translated, because it's part of command syntax. +rmid.syntax.port.missing=\ + -port \u9009\u9879\u9700\u8981\u53c2\u6570 + +# "-log" should not be translated, because it's part of command syntax. +rmid.syntax.log.missing=\ + -log \u9009\u9879\u9700\u8981\u53c2\u6570 + +# "-log" should not be translated, because it's part of command syntax. +rmid.syntax.log.required=\ + \u9700\u8981 -log \u9009\u9879 + +# {0} = the (string) illegal argument in question +rmid.syntax.illegal.option=\ + \u975e\u6cd5\u9009\u9879\uff1a{0} + +# {0} = the (string) reason text that came with a thrown exception +# "Activation.main" should not be translated, because it's a codepoint +rmid.unexpected.exception=\ + Activation.main: \u51fa\u73b0\u5f02\u5e38\uff1a{0} + +# "java.home" should not be translated, because it's a property name +# "ActivatorImpl" should not be translated, because it's a codepoint +rmid.unfound.java.home.property=\ + ActivatorImpl: \u65e0\u6cd5\u5b9a\u4f4d java.home + +# "rmid" should not be translated +rmid.inherited.channel.info=\ + rmid \u901a\u8fc7\u7ee7\u627f\u7684\u4fe1\u9053\u542f\u52a8 + +# "Activation.main" should not be translated, because it's a codepoint +rmid.exec.policy.invalid=\ + Activation.main: \u65e0\u6548\u7684\u53ef\u6267\u884c policy \u7c7b + +# "rmid" should not be translated +rmid.exec.policy.exception=\ + rmid: \u8bd5\u56fe\u83b7\u53d6\u53ef\u6267\u884c\u7b56\u7565\u629b\u51fa: + +# "rmid" should not be translated +rmid.exec.command=\ + rmid: debugExec: \u6b63\u5728\u8fd0\u884c\u201c{0}\u201d + +# "rmid" should not be translated +rmid.group.inactive=\ + rmid: \u6fc0\u6d3b\u7ec4\u65e0\u6548: {0} + +# "Activation.main", "sun.rmi.activation.execPolicy", "ExecPermission" and +# "ExecOptionPermission" should not be translated, since they refer to +# class/permission names. +rmid.exec.perms.inadequate=\ + Activation.main: \u8b66\u544a\uff1a sun.rmi.activation.execPolicy \u7cfb\u7edf\n\ + \u5c5e\u6027\u672a\u88ab\u6307\u5b9a\u4e14 ExecPermissions/ExecOptionPermissions\n\ + \u5747\u672a\u88ab\u6388\u6743\u3002\u968f\u540e\u7684\u6fc0\u6d3b\u5c1d\u8bd5\u5931\u8d25\uff0c\u539f\u56e0\u662f\u5bf9\n\ + ExecPermission/ExecOptionPermission \u7684\u6743\u9650\u68c0\u67e5\u5931\u8d25\u3002 \u6709\u5173\u5982\u4f55\n\ + \u914d\u7f6e rmid \u5b89\u5168\u6027\u7684\u6587\u6863\u8bf4\u660e\uff0c\u8bf7\u53c2\u9605\uff1a \n\ +\n\ + http://java.sun.com/j2se/1.4/docs/tooldocs/solaris/rmid.html\n\ + http://java.sun.com/j2se/1.4/docs/tooldocs/win32/rmid.html\n + +# "rmid", "-port", "-log", "-stop", "-C" and "-J" should not be translated, +# because they are syntax +rmid.usage=\u7528\u6cd5\uff1a{0} <option>\ +\n\ +\n\u5176\u4e2d\uff0c<option> \u5305\u62ec:\ +\n -port <option> \u6307\u5b9a\u4f9b rmid \u4f7f\u7528\u7684\u7aef\u53e3\ +\n -log <directory> \u6307\u5b9a rmid \u5c06\u65e5\u5fd7\u5199\u5165\u7684\u76ee\u5f55\ +\n -stop \u505c\u6b62\u5f53\u524d\u7684 rmid \u8c03\u7528\uff08\u5bf9\u6307\u5b9a\u7aef\u53e3\uff09\ +\n -C<runtime \u6807\u8bb0> \u5411\u6bcf\u4e2a\u5b50\u8fdb\u7a0b\u4f20\u9012\u53c2\u6570\uff08\u6fc0\u6d3b\u7ec4\uff09\ +\n -J<runtime \u6807\u8bb0> \u5411 java \u89e3\u91ca\u7a0b\u5e8f\u4f20\u9012\u53c2\u6570\ +\n\ + +# This means "The currently running activation daemon has been shut down, +# and is about to exit". +rmid.daemon.shutdown=\ + \u6fc0\u6d3b\u7aef\u53e3\u76d1\u63a7\u7a0b\u5e8f\u5173\u95ed + +# "rmid" should not be translated +rmid.restart.group.warning=\ +\nrmid: (\u8b66\u544a) \u91cd\u65b0\u542f\u52a8\u7ec4\u629b\u51fa: + +# "rmid" should not be translated +rmid.restart.service.warning=\ +\nrmid: (\u8b66\u544a) \u91cd\u65b0\u542f\u52a8\u670d\u52a1\u629b\u51fa: + +# "rmid" should not be translated +rmid.log.update.warning=\ +\nrmid: (\u8b66\u544a) \u65e5\u5fd7\u66f4\u65b0\u629b\u51fa: + +# "rmid" should not be translated +rmid.log.snapshot.warning=\ +\nrmid: (\u4e25\u91cd\u8b66\u544a) \u65e5\u5fd7\u5feb\u7167\u629b\u51fa: + +# "rmid" should not be translated +rmid.log.recover.warning=\ +\nrmid: (\u8b66\u544a) {0}: \u6062\u590d\u671f\u95f4\u8df3\u8fc7\u65e5\u5fd7\u8bb0\u5f55: diff --git a/src/share/classes/sun/rmi/server/resources/rmid_zh_TW.properties b/src/share/classes/sun/rmi/server/resources/rmid_zh_TW.properties new file mode 100644 index 000000000..f47e79f79 --- /dev/null +++ b/src/share/classes/sun/rmi/server/resources/rmid_zh_TW.properties @@ -0,0 +1,132 @@ +# +# +# Copyright 2000-2005 Sun Microsystems, Inc. All Rights Reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Sun designates this +# particular file as subject to the "Classpath" exception as provided +# by Sun in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, +# CA 95054 USA or visit www.sun.com if you need additional information or +# have any questions. +# + +# "rmid", inetd", and "wait" should not be translated. +rmid.syntax.exec.invalid=\ + rmid \u5df2\u5f9e inetd \u555f\u52d5\uff0c\u4e14\u72c0\u614b\u7121\u6548 (\u5fc5\u9808\u662f wait) + +# "rmid" and "inetd" should not be translated. +rmid.syntax.port.badarg=\ + \u5982\u679c rmid \u5f9e inetd \u555f\u52d5\uff0c\u5247\u7121\u6cd5\u6307\u5b9a\u9023\u63a5\u57e0 + +# "port" here refers to a TCP port for the server to listen on. +rmid.syntax.port.badnumber=\ + port \u4e0d\u662f\u4e00\u500b\u6578\u5b57 + +# "-port" should not be translated, because it's part of command syntax. +rmid.syntax.port.missing=\ + -port \u9078\u9805\u9700\u8981\u5f15\u6578 + +# "-log" should not be translated, because it's part of command syntax. +rmid.syntax.log.missing=\ + -log \u9078\u9805\u9700\u8981\u5f15\u6578 + +# "-log" should not be translated, because it's part of command syntax. +rmid.syntax.log.required=\ + -log \u9078\u9805\u662f\u5fc5\u9700\u7684 + +# {0} = the (string) illegal argument in question +rmid.syntax.illegal.option=\ + \u975e\u6cd5\u9078\u9805\uff1a{0} + +# {0} = the (string) reason text that came with a thrown exception +# "Activation.main" should not be translated, because it's a codepoint +rmid.unexpected.exception=\ + Activation.main: \u767c\u751f\u7570\u5e38\u72c0\u6cc1\ufe55{0} + +# "java.home" should not be translated, because it's a property name +# "ActivatorImpl" should not be translated, because it's a codepoint +rmid.unfound.java.home.property=\ + ActivatorImpl: \u627e\u4e0d\u5230 java.home \u7684\u6240\u5728 + +# "rmid" should not be translated +rmid.inherited.channel.info=\ + rmid \u4f7f\u7528\u7e7c\u627f\u7684\u901a\u9053\u555f\u52d5 + +# "Activation.main" should not be translated, because it's a codepoint +rmid.exec.policy.invalid=\Activation.main: \u7121\u6548\u7684\u57f7\u884c\u7b56\u7565\u985e\u5225 + +# "rmid" should not be translated +rmid.exec.policy.exception=\ + rmid: \u5617\u8a66\u53d6\u5f97\u57f7\u884c\u7b56\u7565\u62cb\u68c4\uff1a + +# "rmid" should not be translated +rmid.exec.command=\ + rmid: debugExec\uff1a\u57f7\u884c "{0}" + +# "rmid" should not be translated +rmid.group.inactive=\ + rmid:\u555f\u52d5\u7fa4\u7d44\u672a\u5728\u4f7f\u7528\u4e2d\ufe30{0} + +# "Activation.main", "sun.rmi.activation.execPolicy", "ExecPermission" and +# "ExecOptionPermission" should not be translated, since they refer to +# class/permission names. +rmid.exec.perms.inadequate=\ + Activation.main: \u8b66\u544a: sun.rmi.activation.execPolicy \u7cfb\u7d71\n\ + \u5c6c\u6027\u672a\u6307\u5b9a\uff0c\u4e26\u4e14\u672a\u6388\u4e88 ExecPermissions/ExecOptionPermissions\uff1b\n\ + \u5f8c\u7e8c\u7684\u555f\u52d5\u5617\u8a66\u53ef\u80fd\u6703\u56e0\u70ba\u672a\u6210\u529f\u7684\n\ + ExecPermission/ExecOptionPermission \u8a31\u53ef\u6b0a\u6aa2\u67e5\u800c\u5931\u6557\u3002\u5982\u9700\n\ + \u95dc\u65bc\u5982\u4f55\u914d\u7f6e rmid \u5b89\u5168\u7684\u8aaa\u660e\u6587\u4ef6\uff0c\u8acb\u53c3\u8003\uff1a\n\ +\n\ + http://java.sun.com/j2se/1.4/docs/tooldocs/solaris/rmid.html\n\ + http://java.sun.com/j2se/1.4/docs/tooldocs/win32/rmid.html\n + +# "rmid", "-port", "-log", "-stop", "-C" and "-J" should not be translated, +# because they are syntax +rmid.usage=\u7528\u6cd5: {0} <options>\ +\n\ +\n\u5176\u4e2d <options> \u5305\u62ec\uff1a\ +\n -port <port> \u6307\u5b9a\u4f9b rmid \u4f7f\u7528\u7684\u9023\u63a5\u57e0\ +\n -log <directory> \u6307\u5b9a\u4f9b rmid \u5beb\u5165\u65e5\u8a8c\u7684\u76ee\u9304\ +\n -stop \u505c\u6b62\u76ee\u524d rmid \u7684\u547c\u53eb (\u91dd\u5c0d\u6307\u5b9a\u7684\u9023\u63a5\u57e0)\ +\n -C<runtime flag> \u50b3\u905e\u5f15\u6578\u81f3\u6bcf\u4e00\u5b50\u904e\u7a0b (\u4f5c\u7528\u7fa4\u7d44)\ +\n -J<runtime flag> \u50b3\u905e\u5f15\u6578\u81f3 java \u89e3\u8b6f\u7a0b\u5f0f\ +\n\ + +# This means "The currently running activation daemon has been shut down, +# and is about to exit". +rmid.daemon.shutdown=\ + \u505c\u6b62 activation \u5e38\u99d0\u7a0b\u5f0f + +# "rmid" should not be translated +rmid.restart.group.warning=\ +\nrmid: (\u8b66\u544a) \u91cd\u65b0\u555f\u52d5\u7fa4\u7d44\u62cb\u68c4\uff1a + +# "rmid" should not be translated +rmid.restart.service.warning=\ +\nrmid: (\u8b66\u544a) \u91cd\u65b0\u555f\u52d5\u670d\u52d9\u62cb\u68c4\uff1a + +# "rmid" should not be translated +rmid.log.update.warning=\ +\nrmid: (\u8b66\u544a) \u8a18\u9304\u66f4\u65b0\u62cb\u68c4\uff1a + +# "rmid" should not be translated +rmid.log.snapshot.warning=\ +\nrmid: (\u56b4\u91cd) \u8a18\u9304\u5feb\u7167\u62cb\u68c4\uff1a + +# "rmid" should not be translated +rmid.log.recover.warning=\ +\nrmid: (\u8b66\u544a) {0}\uff1a\u5728\u56de\u5fa9\u904e\u7a0b\u4e2d\u7565\u904e\u65e5\u8a8c\u8a18\u9304\uff1a diff --git a/src/share/classes/sun/rmi/transport/Channel.java b/src/share/classes/sun/rmi/transport/Channel.java new file mode 100644 index 000000000..def4f7951 --- /dev/null +++ b/src/share/classes/sun/rmi/transport/Channel.java @@ -0,0 +1,50 @@ +/* + * Copyright 1996-2001 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ +package sun.rmi.transport; + +import java.rmi.RemoteException; + +public interface Channel { + + /** + * Generates a new connection to the endpoint of the address space + * for which this is a channel. + */ + public Connection newConnection() throws RemoteException; + + /** + * Returns the endpoint of the address space for which this is a + * channel. + */ + public Endpoint getEndpoint(); + + /** + * Free the connection generated by this channel. + * @param c The connection + * @param reuse If true, the connection is in a state in which it + * can be reused for another method call. + */ + public void free(Connection conn, boolean reuse) throws RemoteException; +} diff --git a/src/share/classes/sun/rmi/transport/Connection.java b/src/share/classes/sun/rmi/transport/Connection.java new file mode 100644 index 000000000..49f9f651a --- /dev/null +++ b/src/share/classes/sun/rmi/transport/Connection.java @@ -0,0 +1,64 @@ +/* + * Copyright 1996 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ +package sun.rmi.transport; + +import java.io.*; + +public interface Connection { + /** + * Gets the input stream for this connection. + */ + public InputStream getInputStream() throws IOException; + + /* + * Release the input stream for this connection. + */ + public void releaseInputStream() throws IOException; + + /** + * Gets the output stream for this connection + */ + public OutputStream getOutputStream() throws IOException; + + /* + * Release the output stream for this connection. + */ + public void releaseOutputStream() throws IOException; + + /** + * Return true if channel can be used for multiple operations. + */ + public boolean isReusable(); + + /** + * Close connection. + */ + public void close() throws IOException; + + /** + * Returns the channel for this connection. + */ + public Channel getChannel(); +} diff --git a/src/share/classes/sun/rmi/transport/ConnectionInputStream.java b/src/share/classes/sun/rmi/transport/ConnectionInputStream.java new file mode 100644 index 000000000..12ee3c55c --- /dev/null +++ b/src/share/classes/sun/rmi/transport/ConnectionInputStream.java @@ -0,0 +1,162 @@ +/* + * Copyright 1996-2001 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ +package sun.rmi.transport; + +import java.io.*; +import java.util.*; +import java.rmi.RemoteException; +import java.rmi.server.UID; +import sun.rmi.server.MarshalInputStream; +import sun.rmi.runtime.Log; + +/** + * Special stream to keep track of refs being unmarshaled so that + * refs can be ref-counted locally. + * + * @author Ann Wollrath + */ +class ConnectionInputStream extends MarshalInputStream { + + /** indicates whether ack is required for DGC */ + private boolean dgcAckNeeded = false; + + /** Hashtable mapping Endpoints to lists of LiveRefs to register */ + private Map incomingRefTable = new HashMap(5); + + /** identifier for gc ack*/ + private UID ackID; + + /** + * Constructs a marshal input stream using the underlying + * stream "in". + */ + ConnectionInputStream(InputStream in) throws IOException { + super(in); + } + + void readID() throws IOException { + ackID = UID.read((DataInput) this); + } + + /** + * Save reference in order to send "dirty" call after all args/returns + * have been unmarshaled. Save in hashtable incomingRefTable. This + * table is keyed on endpoints, and holds objects of type + * IncomingRefTableEntry. + */ + void saveRef(LiveRef ref) { + Endpoint ep = ref.getEndpoint(); + + // check whether endpoint is already in the hashtable + List refList = (List) incomingRefTable.get(ep); + + if (refList == null) { + refList = new ArrayList(); + incomingRefTable.put(ep, refList); + } + + // add ref to list of refs for endpoint ep + refList.add(ref); + } + + /** + * Add references to DGC table (and possibly send dirty call). + * RegisterRefs now calls DGCClient.referenced on all + * refs with the same endpoint at once to achieve batching of + * calls to the DGC + */ + void registerRefs() throws IOException { + if (!incomingRefTable.isEmpty()) { + Set entrySet = incomingRefTable.entrySet(); + Iterator iter = entrySet.iterator(); + while (iter.hasNext()) { + Map.Entry entry = (Map.Entry) iter.next(); + Endpoint ep = (Endpoint) entry.getKey(); + List refList = (List) entry.getValue(); + DGCClient.registerRefs(ep, refList); + } + } + } + + /** + * Indicate that an ack is required to the distributed + * collector. + */ + void setAckNeeded() { + dgcAckNeeded = true; + } + + /** + * Done with input stream for remote call. Send DGC ack if necessary. + * Allow sending of ack to fail without flagging an error. + */ + void done(Connection c) { + /* + * WARNING: The connection c may have already been freed. It + * is only be safe to use c to obtain c's channel. + */ + + if (dgcAckNeeded) { + Connection conn = null; + Channel ch = null; + boolean reuse = true; + + DGCImpl.dgcLog.log(Log.VERBOSE, "send ack"); + + try { + ch = c.getChannel(); + conn = ch.newConnection(); + DataOutputStream out = + new DataOutputStream(conn.getOutputStream()); + out.writeByte(TransportConstants.DGCAck); + if (ackID == null) { + ackID = new UID(); + } + ackID.write((DataOutput) out); + conn.releaseOutputStream(); + + /* + * Fix for 4221173: if this connection is on top of an + * HttpSendSocket, the DGCAck won't actually get sent until a + * read operation is attempted on the socket. Calling + * available() is the most innocuous way of triggering the + * write. + */ + conn.getInputStream().available(); + conn.releaseInputStream(); + } catch (RemoteException e) { + reuse = false; + } catch (IOException e) { + reuse = false; + } + try { + if (conn != null) + ch.free(conn, reuse); + } catch (RemoteException e){ + // eat exception + } + } + } +} diff --git a/src/share/classes/sun/rmi/transport/ConnectionOutputStream.java b/src/share/classes/sun/rmi/transport/ConnectionOutputStream.java new file mode 100644 index 000000000..22a8e3aeb --- /dev/null +++ b/src/share/classes/sun/rmi/transport/ConnectionOutputStream.java @@ -0,0 +1,109 @@ +/* + * Copyright 1996-2005 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.transport; + +import java.io.IOException; +import java.rmi.server.UID; +import sun.rmi.server.MarshalOutputStream; + +/** + * Special stream to keep track of refs being marshaled as return + * results to determine whether a special ack needs to be sent + * to the distributed collector. + * + * @author Ann Wollrath + */ +class ConnectionOutputStream extends MarshalOutputStream { + + /** connection associated with ConnectionOutputStream */ + private final Connection conn; + /** indicates whether output stream is used to marshal results */ + private final boolean resultStream; + /** identifier for gc ack*/ + private final UID ackID; + + /** to store refs to returned remote object until DGC ack is received */ + private DGCAckHandler dgcAckHandler = null; + + /** + * Constructs an marshal output stream using the underlying + * stream associated with the connection, the parameter c. + * @param c is the Connection object associated with the + * ConnectionOutputStream object being constructed + * @param resultStream indicates whether this stream is used + * to marshal return results + */ + ConnectionOutputStream(Connection conn, boolean resultStream) + throws IOException + { + super(conn.getOutputStream()); + this.conn = conn; + this.resultStream = resultStream; + ackID = resultStream ? new UID() : null; + } + + void writeID() throws IOException { + assert resultStream; + ackID.write(this); + } + + /** + * Returns true if this output stream is used to marshal return + * results; otherwise returns false. + */ + boolean isResultStream() { + return resultStream; + } + + /** + * Saves a reference to the specified object in this stream's + * DGCAckHandler. + **/ + void saveObject(Object obj) { + // should always be accessed from same thread + if (dgcAckHandler == null) { + dgcAckHandler = new DGCAckHandler(ackID); + } + dgcAckHandler.add(obj); + } + + /** + * Returns this stream's DGCAckHandler, or null if it doesn't have + * one (saveObject was not invoked). This method should only be + * invoked after all objects have been written to the stream, + * because future objects written may yet cause a DGCAckHandler to + * be created (by invoking saveObject). + **/ + DGCAckHandler getDGCAckHandler() { + return dgcAckHandler; + } + + void done() { + if (dgcAckHandler != null) { + dgcAckHandler.startTimer(); + } + } +} diff --git a/src/share/classes/sun/rmi/transport/DGCAckHandler.java b/src/share/classes/sun/rmi/transport/DGCAckHandler.java new file mode 100644 index 000000000..37769fc5e --- /dev/null +++ b/src/share/classes/sun/rmi/transport/DGCAckHandler.java @@ -0,0 +1,148 @@ +/* + * Copyright 1996-2005 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.transport; + +import java.rmi.server.UID; +import java.security.AccessController; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import sun.rmi.runtime.RuntimeUtil; +import sun.security.action.GetLongAction; + +/** + * Holds strong references to a set of remote objects, or live remote + * references to remote objects, after they have been marshalled (as + * remote references) as parts of the arguments or the result of a + * remote invocation. The purpose is to prevent remote objects or + * live remote references that might otherwise be determined to be + * unreachable in this VM from being locally garbage collected before + * the receiver has had an opportunity to register the unmarshalled + * remote references for DGC. + * + * The references are held strongly until an acknowledgment has been + * received that the receiver has had an opportunity to process the + * remote references or until a timeout has expired. For remote + * references sent as parts of the arguments of a remote invocation, + * the acknowledgment is the beginning of the response indicating + * completion of the remote invocation. For remote references sent as + * parts of the result of a remote invocation, a UID is included as + * part of the result, and the acknowledgment is a transport-level + * "DGCAck" message containing that UID. + * + * @author Ann Wollrath + * @author Peter Jones + **/ +public class DGCAckHandler { + + /** timeout for holding references without receiving an acknowledgment */ + private static final long dgcAckTimeout = // default 5 minutes + AccessController.doPrivileged( + new GetLongAction("sun.rmi.dgc.ackTimeout", 300000)); + + /** thread pool for scheduling delayed tasks */ + private static final ScheduledExecutorService scheduler = + AccessController.doPrivileged( + new RuntimeUtil.GetInstanceAction()).getScheduler(); + + /** table mapping ack ID to handler */ + private static final Map<UID,DGCAckHandler> idTable = + Collections.synchronizedMap(new HashMap<UID,DGCAckHandler>()); + + private final UID id; + private List<Object> objList = new ArrayList<Object>(); // null if released + private Future<?> task = null; + + /** + * Creates a new DGCAckHandler, associated with the specified UID + * if the argument is not null. + * + * References added to this DGCAckHandler will be held strongly + * until its "release" method is invoked or (after the + * "startTimer" method has been invoked) the timeout has expired. + * If the argument is not null, then invoking the static + * "received" method with the specified UID is equivalent to + * invoking this instance's "release" method. + **/ + DGCAckHandler(UID id) { + this.id = id; + if (id != null) { + assert !idTable.containsKey(id); + idTable.put(id, this); + } + } + + /** + * Adds the specified reference to this DGCAckHandler. + **/ + synchronized void add(Object obj) { + if (objList != null) { + objList.add(obj); + } + } + + /** + * Starts the timer for this DGCAckHandler. After the timeout has + * expired, the references are released even if the acknowledgment + * has not been received. + **/ + synchronized void startTimer() { + if (objList != null && task == null) { + task = scheduler.schedule(new Runnable() { + public void run() { + release(); + } + }, dgcAckTimeout, TimeUnit.MILLISECONDS); + } + } + + /** + * Releases the references held by this DGCAckHandler. + **/ + synchronized void release() { + if (task != null) { + task.cancel(false); + task = null; + } + objList = null; + } + + /** + * Causes the DGCAckHandler associated with the specified UID to + * release its references. + **/ + public static void received(UID id) { + DGCAckHandler h = idTable.remove(id); + if (h != null) { + h.release(); + } + } +} diff --git a/src/share/classes/sun/rmi/transport/DGCClient.java b/src/share/classes/sun/rmi/transport/DGCClient.java new file mode 100644 index 000000000..31784b357 --- /dev/null +++ b/src/share/classes/sun/rmi/transport/DGCClient.java @@ -0,0 +1,806 @@ +/* + * Copyright 1996-2005 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ +package sun.rmi.transport; + +import java.lang.ref.PhantomReference; +import java.lang.ref.ReferenceQueue; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.rmi.ConnectException; +import java.rmi.RemoteException; +import java.rmi.dgc.DGC; +import java.rmi.dgc.Lease; +import java.rmi.dgc.VMID; +import java.rmi.server.ObjID; +import sun.misc.GC; +import sun.rmi.runtime.NewThreadAction; +import sun.rmi.server.UnicastRef; +import sun.rmi.server.Util; +import sun.security.action.GetLongAction; + +/** + * DGCClient implements the client-side of the RMI distributed garbage + * collection system. + * + * The external interface to DGCClient is the "registerRefs" method. + * When a LiveRef to a remote object enters the VM, it needs to be + * registered with the DGCClient to participate in distributed garbage + * collection. + * + * When the first LiveRef to a particular remote object is registered, + * a "dirty" call is made to the server-side distributed garbage + * collector for the remote object, which returns a lease guaranteeing + * that the server-side DGC will not collect the remote object for a + * certain period of time. While LiveRef instances to remote objects + * on a particular server exist, the DGCClient periodically sends more + * "dirty" calls to renew its lease. + * + * The DGCClient tracks the local reachability of registered LiveRef + * instances (using phantom references). When the LiveRef instance + * for a particular remote object becomes garbage collected locally, + * a "clean" call is made to the server-side distributed garbage + * collector, indicating that the server no longer needs to keep the + * remote object alive for this client. + * + * @see java.rmi.dgc.DGC, sun.rmi.transport.DGCImpl + * + * @author Ann Wollrath + * @author Peter Jones + */ +final class DGCClient { + + /** next sequence number for DGC calls (access synchronized on class) */ + private static long nextSequenceNum = Long.MIN_VALUE; + + /** unique identifier for this VM as a client of DGC */ + private static VMID vmid = new VMID(); + + /** lease duration to request (usually ignored by server) */ + private static final long leaseValue = // default 10 minutes + ((Long) AccessController.doPrivileged( + new GetLongAction("java.rmi.dgc.leaseValue", + 600000))).longValue(); + + /** maximum interval between retries of failed clean calls */ + private static final long cleanInterval = // default 3 minutes + ((Long) AccessController.doPrivileged( + new GetLongAction("sun.rmi.dgc.cleanInterval", + 180000))).longValue(); + + /** maximum interval between complete garbage collections of local heap */ + private static final long gcInterval = // default 1 hour + ((Long) AccessController.doPrivileged( + new GetLongAction("sun.rmi.dgc.client.gcInterval", + 3600000))).longValue(); + + /** minimum retry count for dirty calls that fail */ + private static final int dirtyFailureRetries = 5; + + /** retry count for clean calls that fail with ConnectException */ + private static final int cleanFailureRetries = 5; + + /** constant empty ObjID array for lease renewal optimization */ + private static final ObjID[] emptyObjIDArray = new ObjID[0]; + + /** ObjID for server-side DGC object */ + private static final ObjID dgcID = new ObjID(ObjID.DGC_ID); + + /* + * Disallow anyone from creating one of these. + */ + private DGCClient() {} + + /** + * Register the LiveRef instances in the supplied list to participate + * in distributed garbage collection. + * + * All of the LiveRefs in the list must be for remote objects at the + * given endpoint. + */ + static void registerRefs(Endpoint ep, List refs) { + /* + * Look up the given endpoint and register the refs with it. + * The retrieved entry may get removed from the global endpoint + * table before EndpointEntry.registerRefs() is able to acquire + * its lock; in this event, it returns false, and we loop and + * try again. + */ + EndpointEntry epEntry; + do { + epEntry = EndpointEntry.lookup(ep); + } while (!epEntry.registerRefs(refs)); + } + + /** + * Get the next sequence number to be used for a dirty or clean + * operation from this VM. This method should only be called while + * synchronized on the EndpointEntry whose data structures the + * operation affects. + */ + private static synchronized long getNextSequenceNum() { + return nextSequenceNum++; + } + + /** + * Given the length of a lease and the time that it was granted, + * compute the absolute time at which it should be renewed, giving + * room for reasonable computational and communication delays. + */ + private static long computeRenewTime(long grantTime, long duration) { + /* + * REMIND: This algorithm should be more sophisticated, waiting + * a longer fraction of the lease duration for longer leases. + */ + return grantTime + (duration / 2); + } + + /** + * EndpointEntry encapsulates the client-side DGC information specific + * to a particular Endpoint. Of most significance is the table that + * maps LiveRef value to RefEntry objects and the renew/clean thread + * that handles asynchronous client-side DGC operations. + */ + private static class EndpointEntry { + + /** the endpoint that this entry is for */ + private Endpoint endpoint; + /** synthesized reference to the remote server-side DGC */ + private DGC dgc; + + /** table of refs held for endpoint: maps LiveRef to RefEntry */ + private Map refTable = new HashMap(5); + /** set of RefEntry instances from last (failed) dirty call */ + private Set invalidRefs = new HashSet(5); + + /** true if this entry has been removed from the global table */ + private boolean removed = false; + + /** absolute time to renew current lease to this endpoint */ + private long renewTime = Long.MAX_VALUE; + /** absolute time current lease to this endpoint will expire */ + private long expirationTime = Long.MIN_VALUE; + /** count of recent dirty calls that have failed */ + private int dirtyFailures = 0; + /** absolute time of first recent failed dirty call */ + private long dirtyFailureStartTime; + /** (average) elapsed time for recent failed dirty calls */ + private long dirtyFailureDuration; + + /** renew/clean thread for handling lease renewals and clean calls */ + private Thread renewCleanThread; + /** true if renew/clean thread may be interrupted */ + private boolean interruptible = false; + + /** reference queue for phantom references */ + private ReferenceQueue refQueue = new ReferenceQueue(); + /** set of clean calls that need to be made */ + private Set pendingCleans = new HashSet(5); + + /** global endpoint table: maps Endpoint to EndpointEntry */ + private static Map endpointTable = new HashMap(5); + /** handle for GC latency request (for future cancellation) */ + private static GC.LatencyRequest gcLatencyRequest = null; + + /** + * Look up the EndpointEntry for the given Endpoint. An entry is + * created if one does not already exist. + */ + public static EndpointEntry lookup(Endpoint ep) { + synchronized (endpointTable) { + EndpointEntry entry = (EndpointEntry) endpointTable.get(ep); + if (entry == null) { + entry = new EndpointEntry(ep); + endpointTable.put(ep, entry); + /* + * While we are tracking live remote references registered + * in this VM, request a maximum latency for inspecting the + * entire heap from the local garbage collector, to place + * an upper bound on the time to discover remote references + * that have become unreachable (see bugid 4171278). + */ + if (gcLatencyRequest == null) { + gcLatencyRequest = GC.requestLatency(gcInterval); + } + } + return entry; + } + } + + private EndpointEntry(final Endpoint endpoint) { + this.endpoint = endpoint; + try { + LiveRef dgcRef = new LiveRef(dgcID, endpoint, false); + dgc = (DGC) Util.createProxy(DGCImpl.class, + new UnicastRef(dgcRef), true); + } catch (RemoteException e) { + throw new Error("internal error creating DGC stub"); + } + renewCleanThread = (Thread) AccessController.doPrivileged( + new NewThreadAction(new RenewCleanThread(), + "RenewClean-" + endpoint, true)); + renewCleanThread.start(); + } + + /** + * Register the LiveRef instances in the supplied list to participate + * in distributed garbage collection. + * + * This method returns false if this entry was removed from the + * global endpoint table (because it was empty) before these refs + * could be registered. In that case, a new EndpointEntry needs + * to be looked up. + * + * This method must NOT be called while synchronized on this entry. + */ + public boolean registerRefs(List refs) { + assert !Thread.holdsLock(this); + + Set refsToDirty = null; // entries for refs needing dirty + long sequenceNum; // sequence number for dirty call + + synchronized (this) { + if (removed) { + return false; + } + + Iterator iter = refs.iterator(); + while (iter.hasNext()) { + LiveRef ref = (LiveRef) iter.next(); + assert ref.getEndpoint().equals(endpoint); + + RefEntry refEntry = (RefEntry) refTable.get(ref); + if (refEntry == null) { + LiveRef refClone = (LiveRef) ref.clone(); + refEntry = new RefEntry(refClone); + refTable.put(refClone, refEntry); + if (refsToDirty == null) { + refsToDirty = new HashSet(5); + } + refsToDirty.add(refEntry); + } + + refEntry.addInstanceToRefSet(ref); + } + + if (refsToDirty == null) { + return true; + } + + refsToDirty.addAll(invalidRefs); + invalidRefs.clear(); + + sequenceNum = getNextSequenceNum(); + } + + makeDirtyCall(refsToDirty, sequenceNum); + return true; + } + + /** + * Remove the given RefEntry from the ref table. If that makes + * the ref table empty, remove this entry from the global endpoint + * table. + * + * This method must ONLY be called while synchronized on this entry. + */ + private void removeRefEntry(RefEntry refEntry) { + assert Thread.holdsLock(this); + assert !removed; + assert refTable.containsKey(refEntry.getRef()); + + refTable.remove(refEntry.getRef()); + invalidRefs.remove(refEntry); + if (refTable.isEmpty()) { + synchronized (endpointTable) { + endpointTable.remove(endpoint); + Transport transport = endpoint.getOutboundTransport(); + transport.free(endpoint); + /* + * If there are no longer any live remote references + * registered, we are no longer concerned with the + * latency of local garbage collection here. + */ + if (endpointTable.isEmpty()) { + assert gcLatencyRequest != null; + gcLatencyRequest.cancel(); + gcLatencyRequest = null; + } + removed = true; + } + } + } + + /** + * Make a DGC dirty call to this entry's endpoint, for the ObjIDs + * corresponding to the given set of refs and with the given + * sequence number. + * + * This method must NOT be called while synchronized on this entry. + */ + private void makeDirtyCall(Set refEntries, long sequenceNum) { + assert !Thread.holdsLock(this); + + ObjID[] ids; + if (refEntries != null) { + ids = createObjIDArray(refEntries); + } else { + ids = emptyObjIDArray; + } + + long startTime = System.currentTimeMillis(); + try { + Lease lease = + dgc.dirty(ids, sequenceNum, new Lease(vmid, leaseValue)); + long duration = lease.getValue(); + + long newRenewTime = computeRenewTime(startTime, duration); + long newExpirationTime = startTime + duration; + + synchronized (this) { + dirtyFailures = 0; + setRenewTime(newRenewTime); + expirationTime = newExpirationTime; + } + + } catch (Exception e) { + long endTime = System.currentTimeMillis(); + + synchronized (this) { + dirtyFailures++; + + if (dirtyFailures == 1) { + /* + * If this was the first recent failed dirty call, + * reschedule another one immediately, in case there + * was just a transient network problem, and remember + * the start time and duration of this attempt for + * future calculations of the delays between retries. + */ + dirtyFailureStartTime = startTime; + dirtyFailureDuration = endTime - startTime; + setRenewTime(endTime); + } else { + /* + * For each successive failed dirty call, wait for a + * (binary) exponentially increasing delay before + * retrying, to avoid network congestion. + */ + int n = dirtyFailures - 2; + if (n == 0) { + /* + * Calculate the initial retry delay from the + * average time elapsed for each of the first + * two failed dirty calls. The result must be + * at least 1000ms, to prevent a tight loop. + */ + dirtyFailureDuration = + Math.max((dirtyFailureDuration + + (endTime - startTime)) >> 1, 1000); + } + long newRenewTime = + endTime + (dirtyFailureDuration << n); + + /* + * Continue if the last known held lease has not + * expired, or else at least a fixed number of times, + * or at least until we've tried for a fixed amount + * of time (the default lease value we request). + */ + if (newRenewTime < expirationTime || + dirtyFailures < dirtyFailureRetries || + newRenewTime < dirtyFailureStartTime + leaseValue) + { + setRenewTime(newRenewTime); + } else { + /* + * Give up: postpone lease renewals until next + * ref is registered for this endpoint. + */ + setRenewTime(Long.MAX_VALUE); + } + } + + if (refEntries != null) { + /* + * Add all of these refs to the set of refs for this + * endpoint that may be invalid (this VM may not be in + * the server's referenced set), so that we will + * attempt to explicitly dirty them again in the + * future. + */ + invalidRefs.addAll(refEntries); + + /* + * Record that a dirty call has failed for all of these + * refs, so that clean calls for them in the future + * will be strong. + */ + Iterator iter = refEntries.iterator(); + while (iter.hasNext()) { + RefEntry refEntry = (RefEntry) iter.next(); + refEntry.markDirtyFailed(); + } + } + + /* + * If the last known held lease will have expired before + * the next renewal, all refs might be invalid. + */ + if (renewTime >= expirationTime) { + invalidRefs.addAll(refTable.values()); + } + } + } + } + + /** + * Set the absolute time at which the lease for this entry should + * be renewed. + * + * This method must ONLY be called while synchronized on this entry. + */ + private void setRenewTime(long newRenewTime) { + assert Thread.holdsLock(this); + + if (newRenewTime < renewTime) { + renewTime = newRenewTime; + if (interruptible) { + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + renewCleanThread.interrupt(); + return null; + } + }); + } + } else { + renewTime = newRenewTime; + } + } + + /** + * RenewCleanThread handles the asynchronous client-side DGC activity + * for this entry: renewing the leases and making clean calls. + */ + private class RenewCleanThread implements Runnable { + + public void run() { + do { + long timeToWait; + RefEntry.PhantomLiveRef phantom = null; + boolean needRenewal = false; + Set refsToDirty = null; + long sequenceNum = Long.MIN_VALUE; + + synchronized (EndpointEntry.this) { + /* + * Calculate time to block (waiting for phantom + * reference notifications). It is the time until the + * lease renewal should be done, bounded on the low + * end by 1 ms so that the reference queue will always + * get processed, and if there are pending clean + * requests (remaining because some clean calls + * failed), bounded on the high end by the maximum + * clean call retry interval. + */ + long timeUntilRenew = + renewTime - System.currentTimeMillis(); + timeToWait = Math.max(timeUntilRenew, 1); + if (!pendingCleans.isEmpty()) { + timeToWait = Math.min(timeToWait, cleanInterval); + } + + /* + * Set flag indicating that it is OK to interrupt this + * thread now, such as if a earlier lease renewal time + * is set, because we are only going to be blocking + * and can deal with interrupts. + */ + interruptible = true; + } + + try { + /* + * Wait for the duration calculated above for any of + * our phantom references to be enqueued. + */ + phantom = (RefEntry.PhantomLiveRef) + refQueue.remove(timeToWait); + } catch (InterruptedException e) { + } + + synchronized (EndpointEntry.this) { + /* + * Set flag indicating that it is NOT OK to interrupt + * this thread now, because we may be undertaking I/O + * operations that should not be interrupted (and we + * will not be blocking arbitrarily). + */ + interruptible = false; + Thread.interrupted(); // clear interrupted state + + /* + * If there was a phantom reference enqueued, process + * it and all the rest on the queue, generating + * clean requests as necessary. + */ + if (phantom != null) { + processPhantomRefs(phantom); + } + + /* + * Check if it is time to renew this entry's lease. + */ + long currentTime = System.currentTimeMillis(); + if (currentTime > renewTime) { + needRenewal = true; + if (!invalidRefs.isEmpty()) { + refsToDirty = invalidRefs; + invalidRefs = new HashSet(5); + } + sequenceNum = getNextSequenceNum(); + } + } + + if (needRenewal) { + makeDirtyCall(refsToDirty, sequenceNum); + } + + if (!pendingCleans.isEmpty()) { + makeCleanCalls(); + } + } while (!removed || !pendingCleans.isEmpty()); + } + } + + /** + * Process the notification of the given phantom reference and any + * others that are on this entry's reference queue. Each phantom + * reference is removed from its RefEntry's ref set. All ref + * entries that have no more registered instances are collected + * into up to two batched clean call requests: one for refs + * requiring a "strong" clean call, and one for the rest. + * + * This method must ONLY be called while synchronized on this entry. + */ + private void processPhantomRefs(RefEntry.PhantomLiveRef phantom) { + assert Thread.holdsLock(this); + + Set strongCleans = null; + Set normalCleans = null; + + do { + RefEntry refEntry = phantom.getRefEntry(); + refEntry.removeInstanceFromRefSet(phantom); + if (refEntry.isRefSetEmpty()) { + if (refEntry.hasDirtyFailed()) { + if (strongCleans == null) { + strongCleans = new HashSet(5); + } + strongCleans.add(refEntry); + } else { + if (normalCleans == null) { + normalCleans = new HashSet(5); + } + normalCleans.add(refEntry); + } + removeRefEntry(refEntry); + } + } while ((phantom = + (RefEntry.PhantomLiveRef) refQueue.poll()) != null); + + if (strongCleans != null) { + pendingCleans.add( + new CleanRequest(createObjIDArray(strongCleans), + getNextSequenceNum(), true)); + } + if (normalCleans != null) { + pendingCleans.add( + new CleanRequest(createObjIDArray(normalCleans), + getNextSequenceNum(), false)); + } + } + + /** + * CleanRequest holds the data for the parameters of a clean call + * that needs to be made. + */ + private static class CleanRequest { + + final ObjID[] objIDs; + final long sequenceNum; + final boolean strong; + + /** how many times this request has failed */ + int failures = 0; + + CleanRequest(ObjID[] objIDs, long sequenceNum, boolean strong) { + this.objIDs = objIDs; + this.sequenceNum = sequenceNum; + this.strong = strong; + } + } + + /** + * Make all of the clean calls described by the clean requests in + * this entry's set of "pending cleans". Clean requests for clean + * calls that succeed are removed from the "pending cleans" set. + * + * This method must NOT be called while synchronized on this entry. + */ + private void makeCleanCalls() { + assert !Thread.holdsLock(this); + + Iterator iter = pendingCleans.iterator(); + while (iter.hasNext()) { + CleanRequest request = (CleanRequest) iter.next(); + try { + dgc.clean(request.objIDs, request.sequenceNum, vmid, + request.strong); + iter.remove(); + } catch (Exception e) { + /* + * Many types of exceptions here could have been + * caused by a transient failure, so try again a + * few times, but not forever. + */ + if (++request.failures >= cleanFailureRetries) { + iter.remove(); + } + } + } + } + + /** + * Create an array of ObjIDs (needed for the DGC remote calls) + * from the ids in the given set of refs. + */ + private static ObjID[] createObjIDArray(Set refEntries) { + ObjID[] ids = new ObjID[refEntries.size()]; + Iterator iter = refEntries.iterator(); + for (int i = 0; i < ids.length; i++) { + ids[i] = ((RefEntry) iter.next()).getRef().getObjID(); + } + return ids; + } + + /** + * RefEntry encapsulates the client-side DGC information specific + * to a particular LiveRef value. In particular, it contains a + * set of phantom references to all of the instances of the LiveRef + * value registered in the system (but not garbage collected + * locally). + */ + private class RefEntry { + + /** LiveRef value for this entry (not a registered instance) */ + private LiveRef ref; + /** set of phantom references to registered instances */ + private Set refSet = new HashSet(5); + /** true if a dirty call containing this ref has failed */ + private boolean dirtyFailed = false; + + public RefEntry(LiveRef ref) { + this.ref = ref; + } + + /** + * Return the LiveRef value for this entry (not a registered + * instance). + */ + public LiveRef getRef() { + return ref; + } + + /** + * Add a LiveRef to the set of registered instances for this entry. + * + * This method must ONLY be invoked while synchronized on this + * RefEntry's EndpointEntry. + */ + public void addInstanceToRefSet(LiveRef ref) { + assert Thread.holdsLock(EndpointEntry.this); + assert ref.equals(this.ref); + + /* + * Only keep a phantom reference to the registered instance, + * so that it can be garbage collected normally (and we can be + * notified when that happens). + */ + refSet.add(new PhantomLiveRef(ref)); + } + + /** + * Remove a PhantomLiveRef from the set of registered instances. + * + * This method must ONLY be invoked while synchronized on this + * RefEntry's EndpointEntry. + */ + public void removeInstanceFromRefSet(PhantomLiveRef phantom) { + assert Thread.holdsLock(EndpointEntry.this); + assert refSet.contains(phantom); + refSet.remove(phantom); + } + + /** + * Return true if there are no registered LiveRef instances for + * this entry still reachable in this VM. + * + * This method must ONLY be invoked while synchronized on this + * RefEntry's EndpointEntry. + */ + public boolean isRefSetEmpty() { + assert Thread.holdsLock(EndpointEntry.this); + return refSet.size() == 0; + } + + /** + * Record that a dirty call that explicitly contained this + * entry's ref has failed. + * + * This method must ONLY be invoked while synchronized on this + * RefEntry's EndpointEntry. + */ + public void markDirtyFailed() { + assert Thread.holdsLock(EndpointEntry.this); + dirtyFailed = true; + } + + /** + * Return true if a dirty call that explicitly contained this + * entry's ref has failed (and therefore a clean call for this + * ref needs to be marked "strong"). + * + * This method must ONLY be invoked while synchronized on this + * RefEntry's EndpointEntry. + */ + public boolean hasDirtyFailed() { + assert Thread.holdsLock(EndpointEntry.this); + return dirtyFailed; + } + + /** + * PhantomLiveRef is a PhantomReference to a LiveRef instance, + * used to detect when the LiveRef becomes permanently + * unreachable in this VM. + */ + private class PhantomLiveRef extends PhantomReference { + + public PhantomLiveRef(LiveRef ref) { + super(ref, EndpointEntry.this.refQueue); + } + + public RefEntry getRefEntry() { + return RefEntry.this; + } + } + } + } +} diff --git a/src/share/classes/sun/rmi/transport/DGCImpl.java b/src/share/classes/sun/rmi/transport/DGCImpl.java new file mode 100644 index 000000000..7b771fc42 --- /dev/null +++ b/src/share/classes/sun/rmi/transport/DGCImpl.java @@ -0,0 +1,340 @@ +/* + * Copyright 1996-2005 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ +package sun.rmi.transport; + +import java.rmi.Remote; +import java.rmi.RemoteException; +import java.rmi.dgc.DGC; +import java.rmi.dgc.Lease; +import java.rmi.dgc.VMID; +import java.rmi.server.LogStream; +import java.rmi.server.ObjID; +import java.rmi.server.RemoteServer; +import java.rmi.server.ServerNotActiveException; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import sun.rmi.runtime.Log; +import sun.rmi.runtime.RuntimeUtil; +import sun.rmi.server.UnicastRef; +import sun.rmi.server.UnicastServerRef; +import sun.rmi.server.Util; +import sun.security.action.GetLongAction; +import sun.security.action.GetPropertyAction; + +/** + * This class implements the guts of the server-side distributed GC + * algorithm + * + * @author Ann Wollrath + */ +final class DGCImpl implements DGC { + + /* dgc system log */ + static final Log dgcLog = Log.getLog("sun.rmi.dgc", "dgc", + LogStream.parseLevel(AccessController.doPrivileged( + new GetPropertyAction("sun.rmi.dgc.logLevel")))); + + /** lease duration to grant to clients */ + private static final long leaseValue = // default 10 minutes + AccessController.doPrivileged( + new GetLongAction("java.rmi.dgc.leaseValue", 600000)); + + /** lease check interval; default is half of lease grant duration */ + private static final long leaseCheckInterval = + AccessController.doPrivileged( + new GetLongAction("sun.rmi.dgc.checkInterval", leaseValue / 2)); + + /** thread pool for scheduling delayed tasks */ + private static final ScheduledExecutorService scheduler = + AccessController.doPrivileged( + new RuntimeUtil.GetInstanceAction()).getScheduler(); + + /** remote implementation of DGC interface for this VM */ + private static DGCImpl dgc; + /** table that maps VMID to LeaseInfo */ + private Map<VMID,LeaseInfo> leaseTable = new HashMap<VMID,LeaseInfo>(); + /** checks for lease expiration */ + private Future<?> checker = null; + + /** + * Return the remote implementation of the DGC interface for + * this VM. + */ + static DGCImpl getDGCImpl() { + return dgc; + } + + /** + * Construct a new server-side remote object collector at + * a particular port. Disallow construction from outside. + */ + private DGCImpl() {} + + /** + * The dirty call adds the VMID "vmid" to the set of clients + * that hold references to the object associated with the ObjID + * id. The long "sequenceNum" is used to detect late dirty calls. If + * the VMID "vmid" is null, a VMID will be generated on the + * server (for use by the client in subsequent calls) and + * returned. + * + * The client must call the "dirty" method to renew the lease + * before the "lease" time expires or all references to remote + * objects in this VM that the client holds are considered + * "unreferenced". + */ + public Lease dirty(ObjID[] ids, long sequenceNum, Lease lease) { + VMID vmid = lease.getVMID(); + /* + * The server specifies the lease value; the client has + * no say in the matter. + */ + long duration = leaseValue; + + if (dgcLog.isLoggable(Log.VERBOSE)) { + dgcLog.log(Log.VERBOSE, "vmid = " + vmid); + } + + // create a VMID if one wasn't supplied + if (vmid == null) { + vmid = new VMID(); + + if (dgcLog.isLoggable(Log.BRIEF)) { + String clientHost; + try { + clientHost = RemoteServer.getClientHost(); + } catch (ServerNotActiveException e) { + clientHost = "<unknown host>"; + } + dgcLog.log(Log.BRIEF, " assigning vmid " + vmid + + " to client " + clientHost); + } + } + + lease = new Lease(vmid, duration); + // record lease information + synchronized (leaseTable) { + LeaseInfo info = leaseTable.get(vmid); + if (info == null) { + leaseTable.put(vmid, new LeaseInfo(vmid, duration)); + if (checker == null) { + checker = scheduler.scheduleWithFixedDelay( + new Runnable() { + public void run() { + checkLeases(); + } + }, + leaseCheckInterval, + leaseCheckInterval, TimeUnit.MILLISECONDS); + } + } else { + info.renew(duration); + } + } + + for (ObjID id : ids) { + if (dgcLog.isLoggable(Log.VERBOSE)) { + dgcLog.log(Log.VERBOSE, "id = " + id + + ", vmid = " + vmid + ", duration = " + duration); + } + + ObjectTable.referenced(id, sequenceNum, vmid); + } + + // return the VMID used + return lease; + } + + /** + * The clean call removes the VMID from the set of clients + * that hold references to the object associated with the LiveRef + * ref. The sequence number is used to detect late clean calls. If the + * argument "strong" is true, then the clean call is a result of a + * failed "dirty" call, thus the sequence number for the VMID needs + * to be remembered until the client goes away. + */ + public void clean(ObjID[] ids, long sequenceNum, VMID vmid, boolean strong) + { + for (ObjID id : ids) { + if (dgcLog.isLoggable(Log.VERBOSE)) { + dgcLog.log(Log.VERBOSE, "id = " + id + + ", vmid = " + vmid + ", strong = " + strong); + } + + ObjectTable.unreferenced(id, sequenceNum, vmid, strong); + } + } + + /** + * Register interest in receiving a callback when this VMID + * becomes inaccessible. + */ + void registerTarget(VMID vmid, Target target) { + synchronized (leaseTable) { + LeaseInfo info = leaseTable.get(vmid); + if (info == null) { + target.vmidDead(vmid); + } else { + info.notifySet.add(target); + } + } + } + + /** + * Remove notification request. + */ + void unregisterTarget(VMID vmid, Target target) { + synchronized (leaseTable) { + LeaseInfo info = leaseTable.get(vmid); + if (info != null) { + info.notifySet.remove(target); + } + } + } + + /** + * Check if leases have expired. If a lease has expired, remove + * it from the table and notify all interested parties that the + * VMID is essentially "dead". + * + * @return if true, there are leases outstanding; otherwise leases + * no longer need to be checked + */ + private void checkLeases() { + long time = System.currentTimeMillis(); + + /* List of vmids that need to be removed from the leaseTable */ + List<LeaseInfo> toUnregister = new ArrayList<LeaseInfo>(); + + /* Build a list of leaseInfo objects that need to have + * targets removed from their notifySet. Remove expired + * leases from leaseTable. + */ + synchronized (leaseTable) { + Iterator<LeaseInfo> iter = leaseTable.values().iterator(); + while (iter.hasNext()) { + LeaseInfo info = iter.next(); + if (info.expired(time)) { + toUnregister.add(info); + iter.remove(); + } + } + + if (leaseTable.isEmpty()) { + checker.cancel(false); + checker = null; + } + } + + /* Notify and unegister targets without holding the lock on + * the leaseTable so we avoid deadlock. + */ + for (LeaseInfo info : toUnregister) { + for (Target target : info.notifySet) { + target.vmidDead(info.vmid); + } + } + } + + static { + /* + * "Export" the singleton DGCImpl in a context isolated from + * the arbitrary current thread context. + */ + AccessController.doPrivileged(new PrivilegedAction<Void>() { + public Void run() { + ClassLoader savedCcl = + Thread.currentThread().getContextClassLoader(); + try { + Thread.currentThread().setContextClassLoader( + ClassLoader.getSystemClassLoader()); + + /* + * Put remote collector object in table by hand to prevent + * listen on port. (UnicastServerRef.exportObject would + * cause transport to listen.) + */ + try { + dgc = new DGCImpl(); + ObjID dgcID = new ObjID(ObjID.DGC_ID); + LiveRef ref = new LiveRef(dgcID, 0); + UnicastServerRef disp = new UnicastServerRef(ref); + Remote stub = + Util.createProxy(DGCImpl.class, + new UnicastRef(ref), true); + disp.setSkeleton(dgc); + Target target = + new Target(dgc, disp, stub, dgcID, true); + ObjectTable.putTarget(target); + } catch (RemoteException e) { + throw new Error( + "exception initializing server-side DGC", e); + } + } finally { + Thread.currentThread().setContextClassLoader(savedCcl); + } + return null; + } + }); + } + + private static class LeaseInfo { + VMID vmid; + long expiration; + Set<Target> notifySet = new HashSet<Target>(); + + LeaseInfo(VMID vmid, long lease) { + this.vmid = vmid; + expiration = System.currentTimeMillis() + lease; + } + + synchronized void renew(long lease) { + long newExpiration = System.currentTimeMillis() + lease; + if (newExpiration > expiration) + expiration = newExpiration; + } + + boolean expired(long time) { + if (expiration < time) { + if (dgcLog.isLoggable(Log.BRIEF)) { + dgcLog.log(Log.BRIEF, vmid.toString()); + } + return true; + } else { + return false; + } + } + } +} diff --git a/src/share/classes/sun/rmi/transport/Endpoint.java b/src/share/classes/sun/rmi/transport/Endpoint.java new file mode 100644 index 000000000..37e0d6971 --- /dev/null +++ b/src/share/classes/sun/rmi/transport/Endpoint.java @@ -0,0 +1,55 @@ +/* + * Copyright 1996-2003 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ +package sun.rmi.transport; + +import java.rmi.RemoteException; +import java.rmi.Remote; +import java.rmi.server.ObjID; +import java.rmi.server.RemoteServer; + +public interface Endpoint { + /** + * Return a channel that generates connections to the remote + * endpoint. + */ + Channel getChannel(); + + /** + * Export the object so that it can accept incoming calls at + * the endpoint. + */ + void exportObject(Target target) + throws RemoteException; + + /** + * Returns the transport for incoming connections to this endpoint. + **/ + Transport getInboundTransport(); + + /** + * Returns transport for making connections to remote endpoints. + **/ + Transport getOutboundTransport(); +} diff --git a/src/share/classes/sun/rmi/transport/LiveRef.java b/src/share/classes/sun/rmi/transport/LiveRef.java new file mode 100644 index 000000000..f8810c0d2 --- /dev/null +++ b/src/share/classes/sun/rmi/transport/LiveRef.java @@ -0,0 +1,317 @@ +/* + * Copyright 1996-2005 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.transport; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.rmi.Remote; +import java.rmi.RemoteException; +import java.rmi.server.ObjID; +import java.rmi.server.RMIClientSocketFactory; +import java.rmi.server.RMIServerSocketFactory; +import java.util.Arrays; +import sun.rmi.transport.tcp.TCPEndpoint; + +/** + * NOTE: There is a JDK-internal dependency on the existence of this + * class and its getClientSocketFactory method in the implementation + * of javax.management.remote.rmi.RMIConnector. + **/ +public class LiveRef implements Cloneable { + /** wire representation for the object*/ + private final Endpoint ep; + private final ObjID id; + + /** cached connection service for the object */ + private transient Channel ch; + + /** flag to indicate whether this ref specifies a local server or + * is a ref for a remote object (surrogate) + */ + private final boolean isLocal; + + /** + * Construct a "well-known" live reference to a remote object + * @param isLocalServer If true, indicates this ref specifies a local + * server in this address space; if false, the ref is for a remote + * object (hence a surrogate or proxy) in another address space. + */ + public LiveRef(ObjID objID, Endpoint endpoint, boolean isLocal) { + ep = endpoint; + id = objID; + this.isLocal = isLocal; + } + + /** + * Construct a new live reference for a server object in the local + * address space. + */ + public LiveRef(int port) { + this((new ObjID()), port); + } + + /** + * Construct a new live reference for a server object in the local + * address space, to use sockets of the specified type. + */ + public LiveRef(int port, + RMIClientSocketFactory csf, + RMIServerSocketFactory ssf) + { + this((new ObjID()), port, csf, ssf); + } + + /** + * Construct a new live reference for a "well-known" server object + * in the local address space. + */ + public LiveRef(ObjID objID, int port) { + this(objID, TCPEndpoint.getLocalEndpoint(port), true); + } + + /** + * Construct a new live reference for a "well-known" server object + * in the local address space, to use sockets of the specified type. + */ + public LiveRef(ObjID objID, int port, RMIClientSocketFactory csf, + RMIServerSocketFactory ssf) + { + this(objID, TCPEndpoint.getLocalEndpoint(port, csf, ssf), true); + } + + /** + * Return a shallow copy of this ref. + */ + public Object clone() { + try { + LiveRef newRef = (LiveRef) super.clone(); + return newRef; + } catch (CloneNotSupportedException e) { + throw new InternalError(e.toString()); + } + } + + /** + * Return the port number associated with this ref. + */ + public int getPort() { + return ((TCPEndpoint) ep).getPort(); + } + + /** + * Return the client socket factory associated with this ref. + * + * NOTE: There is a JDK-internal dependency on the existence of + * this method in the implementation of + * javax.management.remote.rmi.RMIConnector. + **/ + public RMIClientSocketFactory getClientSocketFactory() { + return ((TCPEndpoint) ep).getClientSocketFactory(); + } + + /** + * Return the server socket factory associated with this ref. + */ + public RMIServerSocketFactory getServerSocketFactory() { + return ((TCPEndpoint) ep).getServerSocketFactory(); + } + + /** + * Export the object to accept incoming calls. + */ + public void exportObject(Target target) throws RemoteException { + ep.exportObject(target); + } + + public Channel getChannel() throws RemoteException { + if (ch == null) { + ch = ep.getChannel(); + } + return ch; + } + + public ObjID getObjID() { + return id; + } + + Endpoint getEndpoint() { + return ep; + } + + public String toString() { + String type; + + if (isLocal) + type = "local"; + else + type = "remote"; + return "[endpoint:" + ep + "(" + type + ")," + + "objID:" + id + "]"; + } + + public int hashCode() { + return id.hashCode(); + } + + public boolean equals(Object obj) { + if (obj != null && obj instanceof LiveRef) { + LiveRef ref = (LiveRef) obj; + + return (ep.equals(ref.ep) && id.equals(ref.id) && + isLocal == ref.isLocal); + } else { + return false; + } + } + + public boolean remoteEquals(Object obj) { + if (obj != null && obj instanceof LiveRef) { + LiveRef ref = (LiveRef) obj; + + TCPEndpoint thisEp = ((TCPEndpoint) ep); + TCPEndpoint refEp = ((TCPEndpoint) ref.ep); + + RMIClientSocketFactory thisClientFactory = + thisEp.getClientSocketFactory(); + RMIClientSocketFactory refClientFactory = + refEp.getClientSocketFactory(); + + /** + * Fix for 4254103: LiveRef.remoteEquals should not fail + * if one of the objects in the comparison has a null + * server socket. Comparison should only consider the + * following criteria: + * + * hosts, ports, client socket factories and object IDs. + */ + if (thisEp.getPort() != refEp.getPort() || + !thisEp.getHost().equals(refEp.getHost())) + { + return false; + } + if ((thisClientFactory == null) ^ (refClientFactory == null)) { + return false; + } + if ((thisClientFactory != null) && + !((thisClientFactory.getClass() == + refClientFactory.getClass()) && + (thisClientFactory.equals(refClientFactory)))) + { + return false; + } + return (id.equals(ref.id)); + } else { + return false; + } + } + + public void write(ObjectOutput out, boolean useNewFormat) + throws IOException + { + boolean isResultStream = false; + if (out instanceof ConnectionOutputStream) { + ConnectionOutputStream stream = (ConnectionOutputStream) out; + isResultStream = stream.isResultStream(); + /* + * Ensure that referential integrity is not broken while + * this LiveRef is in transit. If it is being marshalled + * as part of a result, it may not otherwise be strongly + * reachable after the remote call has completed; even if + * it is being marshalled as part of an argument, the VM + * may determine that the reference on the stack is no + * longer reachable after marshalling (see 6181943)-- + * therefore, tell the stream to save a reference until a + * timeout expires or, for results, a DGCAck message has + * been received from the caller, or for arguments, the + * remote call has completed. For a "local" LiveRef, save + * a reference to the impl directly, because the impl is + * not reachable from the LiveRef (see 4114579); + * otherwise, save a reference to the LiveRef, for the + * client-side DGC to watch over. (Also see 4017232.) + */ + if (isLocal) { + ObjectEndpoint oe = + new ObjectEndpoint(id, ep.getInboundTransport()); + Target target = ObjectTable.getTarget(oe); + + if (target != null) { + Remote impl = target.getImpl(); + if (impl != null) { + stream.saveObject(impl); + } + } + } else { + stream.saveObject(this); + } + } + // All together now write out the endpoint, id, and flag + + // (need to choose whether or not to use old JDK1.1 endpoint format) + if (useNewFormat) { + ((TCPEndpoint) ep).write(out); + } else { + ((TCPEndpoint) ep).writeHostPortFormat(out); + } + id.write(out); + out.writeBoolean(isResultStream); + } + + public static LiveRef read(ObjectInput in, boolean useNewFormat) + throws IOException, ClassNotFoundException + { + Endpoint ep; + ObjID id; + + // Now read in the endpoint, id, and result flag + // (need to choose whether or not to read old JDK1.1 endpoint format) + if (useNewFormat) { + ep = TCPEndpoint.read(in); + } else { + ep = TCPEndpoint.readHostPortFormat(in); + } + id = ObjID.read(in); + boolean isResultStream = in.readBoolean(); + + LiveRef ref = new LiveRef(id, ep, false); + + if (in instanceof ConnectionInputStream) { + ConnectionInputStream stream = (ConnectionInputStream)in; + // save ref to send "dirty" call after all args/returns + // have been unmarshaled. + stream.saveRef(ref); + if (isResultStream) { + // set flag in stream indicating that remote objects were + // unmarshaled. A DGC ack should be sent by the transport. + stream.setAckNeeded(); + } + } else { + DGCClient.registerRefs(ep, Arrays.asList(new LiveRef[] { ref })); + } + + return ref; + } +} diff --git a/src/share/classes/sun/rmi/transport/ObjectEndpoint.java b/src/share/classes/sun/rmi/transport/ObjectEndpoint.java new file mode 100644 index 000000000..fd376c9e5 --- /dev/null +++ b/src/share/classes/sun/rmi/transport/ObjectEndpoint.java @@ -0,0 +1,90 @@ +/* + * Copyright 2003 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ +package sun.rmi.transport; + +import java.rmi.server.ObjID; + +/** + * An object used as a key to the object table that maps an + * instance of this class to a Target. + * + * @author Ann Wollrath + **/ +class ObjectEndpoint { + + private final ObjID id; + private final Transport transport; + + /** + * Constructs a new ObjectEndpoint instance with the specified id and + * transport. The specified id must be non-null, and the specified + * transport must either be non-null or the specified id must be + * equivalent to an ObjID constructed with ObjID.DGC_ID. + * + * @param id the object identifier + * @param transport the transport + * @throws NullPointerException if id is null + **/ + ObjectEndpoint(ObjID id, Transport transport) { + if (id == null) { + throw new NullPointerException(); + } + assert transport != null || id.equals(new ObjID(ObjID.DGC_ID)); + + this.id = id; + this.transport = transport; + } + + /** + * Compares the specified object with this object endpoint for + * equality. + * + * This method returns true if and only if the specified object is an + * ObjectEndpoint instance with the same object identifier and + * transport as this object. + **/ + public boolean equals(Object obj) { + if (obj instanceof ObjectEndpoint) { + ObjectEndpoint oe = (ObjectEndpoint) obj; + return id.equals(oe.id) && transport == oe.transport; + } else { + return false; + } + } + + /** + * Returns the hash code value for this object endpoint. + */ + public int hashCode() { + return id.hashCode() ^ (transport != null ? transport.hashCode() : 0); + } + + /** + * Returns a string representation for this object endpoint. + */ + public String toString() { + return id.toString(); + } +} diff --git a/src/share/classes/sun/rmi/transport/ObjectTable.java b/src/share/classes/sun/rmi/transport/ObjectTable.java new file mode 100644 index 000000000..e431ecd74 --- /dev/null +++ b/src/share/classes/sun/rmi/transport/ObjectTable.java @@ -0,0 +1,370 @@ +/* + * Copyright 1996-2005 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ +package sun.rmi.transport; + +import java.lang.ref.ReferenceQueue; +import java.rmi.NoSuchObjectException; +import java.rmi.Remote; +import java.rmi.dgc.VMID; +import java.rmi.server.ExportException; +import java.rmi.server.ObjID; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.HashMap; +import java.util.Map; +import sun.misc.GC; +import sun.rmi.runtime.Log; +import sun.rmi.runtime.NewThreadAction; +import sun.security.action.GetLongAction; + +/** + * Object table shared by all implementors of the Transport interface. + * This table maps object ids to remote object targets in this address + * space. + * + * @author Ann Wollrath + * @author Peter Jones + */ +public final class ObjectTable { + + /** maximum interval between complete garbage collections of local heap */ + private final static long gcInterval = // default 1 hour + AccessController.doPrivileged( + new GetLongAction("sun.rmi.dgc.server.gcInterval", 3600000)); + + /** + * lock guarding objTable and implTable. + * Holders MAY acquire a Target instance's lock or keepAliveLock. + */ + private static final Object tableLock = new Object(); + + /** tables mapping to Target, keyed from ObjectEndpoint and impl object */ + private static final Map<ObjectEndpoint,Target> objTable = + new HashMap<ObjectEndpoint,Target>(); + private static final Map<WeakRef,Target> implTable = + new HashMap<WeakRef,Target>(); + + /** + * lock guarding keepAliveCount, reaper, and gcLatencyRequest. + * Holders may NOT acquire a Target instance's lock or tableLock. + */ + private static final Object keepAliveLock = new Object(); + + /** count of non-permanent objects in table or still processing calls */ + private static int keepAliveCount = 0; + + /** thread to collect unreferenced objects from table */ + private static Thread reaper = null; + + /** queue notified when weak refs in the table are cleared */ + static final ReferenceQueue reapQueue = new ReferenceQueue(); + + /** handle for GC latency request (for future cancellation) */ + private static GC.LatencyRequest gcLatencyRequest = null; + + /* + * Disallow anyone from creating one of these. + */ + private ObjectTable() {} + + /** + * Returns the target associated with the object id. + */ + static Target getTarget(ObjectEndpoint oe) { + synchronized (tableLock) { + return objTable.get(oe); + } + } + + /** + * Returns the target associated with the remote object + */ + public static Target getTarget(Remote impl) { + synchronized (tableLock) { + return implTable.get(new WeakRef(impl)); + } + } + + /** + * Returns the stub for the remote object <b>obj</b> passed + * as a parameter. This operation is only valid <i>after</i> + * the object has been exported. + * + * @return the stub for the remote object, <b>obj</b>. + * @exception NoSuchObjectException if the stub for the + * remote object could not be found. + */ + public static Remote getStub(Remote impl) + throws NoSuchObjectException + { + Target target = getTarget(impl); + if (target == null) { + throw new NoSuchObjectException("object not exported"); + } else { + return target.getStub(); + } + } + + /** + * Remove the remote object, obj, from the RMI runtime. If + * successful, the object can no longer accept incoming RMI calls. + * If the force parameter is true, the object is forcibly unexported + * even if there are pending calls to the remote object or the + * remote object still has calls in progress. If the force + * parameter is false, the object is only unexported if there are + * no pending or in progress calls to the object. + * + * @param obj the remote object to be unexported + * @param force if true, unexports the object even if there are + * pending or in-progress calls; if false, only unexports the object + * if there are no pending or in-progress calls + * @return true if operation is successful, false otherwise + * @exception NoSuchObjectException if the remote object is not + * currently exported + */ + public static boolean unexportObject(Remote obj, boolean force) + throws java.rmi.NoSuchObjectException + { + synchronized (tableLock) { + Target target = getTarget(obj); + if (target == null) { + throw new NoSuchObjectException("object not exported"); + } else { + if (target.unexport(force)) { + removeTarget(target); + return true; + } else { + return false; + } + } + } + } + + /** + * Add target to object table. If it is not a permanent entry, then + * make sure that reaper thread is running to remove collected entries + * and keep VM alive. + */ + static void putTarget(Target target) throws ExportException { + ObjectEndpoint oe = target.getObjectEndpoint(); + WeakRef weakImpl = target.getWeakImpl(); + + if (DGCImpl.dgcLog.isLoggable(Log.VERBOSE)) { + DGCImpl.dgcLog.log(Log.VERBOSE, "add object " + oe); + } + + Remote impl = target.getImpl(); + if (impl == null) { + throw new ExportException( + "internal error: attempt to export collected object"); + } + + synchronized (tableLock) { + if (objTable.containsKey(oe)) { + throw new ExportException( + "internal error: ObjID already in use"); + } else if (implTable.containsKey(weakImpl)) { + throw new ExportException("object already exported"); + } + + objTable.put(oe, target); + implTable.put(weakImpl, target); + + if (!target.isPermanent()) { + incrementKeepAliveCount(); + } + } + } + + /** + * Remove target from object table. + * + * NOTE: This method must only be invoked while synchronized on + * the "tableLock" object, because it does not do so itself. + */ + private static void removeTarget(Target target) { + // assert Thread.holdsLock(tableLock); + + ObjectEndpoint oe = target.getObjectEndpoint(); + WeakRef weakImpl = target.getWeakImpl(); + + if (DGCImpl.dgcLog.isLoggable(Log.VERBOSE)) { + DGCImpl.dgcLog.log(Log.VERBOSE, "remove object " + oe); + } + + objTable.remove(oe); + implTable.remove(weakImpl); + + target.markRemoved(); // handles decrementing keep-alive count + } + + /** + * Process client VM signalling reference for given ObjID: forward to + * correspoding Target entry. If ObjID is not found in table, + * no action is taken. + */ + static void referenced(ObjID id, long sequenceNum, VMID vmid) { + synchronized (tableLock) { + ObjectEndpoint oe = + new ObjectEndpoint(id, Transport.currentTransport()); + Target target = objTable.get(oe); + if (target != null) { + target.referenced(sequenceNum, vmid); + } + } + } + + /** + * Process client VM dropping reference for given ObjID: forward to + * correspoding Target entry. If ObjID is not found in table, + * no action is taken. + */ + static void unreferenced(ObjID id, long sequenceNum, VMID vmid, + boolean strong) + { + synchronized (tableLock) { + ObjectEndpoint oe = + new ObjectEndpoint(id, Transport.currentTransport()); + Target target = objTable.get(oe); + if (target != null) + target.unreferenced(sequenceNum, vmid, strong); + } + } + + /** + * Increments the "keep-alive count". + * + * The "keep-alive count" is the number of non-permanent remote objects + * that are either in the object table or still have calls in progress. + * Therefore, this method should be invoked exactly once for every + * non-permanent remote object exported (a remote object must be + * exported before it can have any calls in progress). + * + * The VM is "kept alive" while the keep-alive count is greater than + * zero; this is accomplished by keeping a non-daemon thread running. + * + * Because non-permanent objects are those that can be garbage + * collected while exported, and thus those for which the "reaper" + * thread operates, the reaper thread also serves as the non-daemon + * VM keep-alive thread; a new reaper thread is created if necessary. + */ + static void incrementKeepAliveCount() { + synchronized (keepAliveLock) { + keepAliveCount++; + + if (reaper == null) { + reaper = AccessController.doPrivileged( + new NewThreadAction(new Reaper(), "Reaper", false)); + reaper.start(); + } + + /* + * While there are non-"permanent" objects in the object table, + * request a maximum latency for inspecting the entire heap + * from the local garbage collector, to place an upper bound + * on the time to discover remote objects that have become + * unreachable (and thus can be removed from the table). + */ + if (gcLatencyRequest == null) { + gcLatencyRequest = GC.requestLatency(gcInterval); + } + } + } + + /** + * Decrements the "keep-alive count". + * + * The "keep-alive count" is the number of non-permanent remote objects + * that are either in the object table or still have calls in progress. + * Therefore, this method should be invoked exactly once for every + * previously-exported non-permanent remote object that both has been + * removed from the object table and has no calls still in progress. + * + * If the keep-alive count is decremented to zero, then the current + * reaper thread is terminated to cease keeping the VM alive (and + * because there are no more non-permanent remote objects to reap). + */ + static void decrementKeepAliveCount() { + synchronized (keepAliveLock) { + keepAliveCount--; + + if (keepAliveCount == 0) { + if (!(reaper != null)) { throw new AssertionError(); } + AccessController.doPrivileged(new PrivilegedAction<Void>() { + public Void run() { + reaper.interrupt(); + return null; + } + }); + reaper = null; + + /* + * If there are no longer any non-permanent objects in the + * object table, we are no longer concerned with the latency + * of local garbage collection here. + */ + gcLatencyRequest.cancel(); + gcLatencyRequest = null; + } + } + } + + /** + * The Reaper thread waits for notifications that weak references in the + * object table have been cleared. When it receives a notification, it + * removes the corresponding entry from the table. + * + * Since the Reaper is created as a non-daemon thread, it also serves + * to keep the VM from exiting while there are objects in the table + * (other than permanent entries that should neither be reaped nor + * keep the VM alive). + */ + private static class Reaper implements Runnable { + + public void run() { + try { + do { + // wait for next cleared weak reference + WeakRef weakImpl = (WeakRef) reapQueue.remove(); + + synchronized (tableLock) { + Target target = implTable.get(weakImpl); + if (target != null) { + if (!target.isEmpty()) { + throw new Error( + "object with known references collected"); + } else if (target.isPermanent()) { + throw new Error("permanent object collected"); + } + removeTarget(target); + } + } + } while (!Thread.interrupted()); + } catch (InterruptedException e) { + // pass away if interrupted + } + } + } +} diff --git a/src/share/classes/sun/rmi/transport/StreamRemoteCall.java b/src/share/classes/sun/rmi/transport/StreamRemoteCall.java new file mode 100644 index 000000000..5a9ef0e1f --- /dev/null +++ b/src/share/classes/sun/rmi/transport/StreamRemoteCall.java @@ -0,0 +1,312 @@ +/* + * Copyright 1996-2005 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.transport; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.StreamCorruptedException; +import java.rmi.RemoteException; +import java.rmi.MarshalException; +import java.rmi.UnmarshalException; +import java.rmi.server.ObjID; +import java.rmi.server.RemoteCall; +import sun.rmi.runtime.Log; +import sun.rmi.server.UnicastRef; +import sun.rmi.transport.tcp.TCPEndpoint; + +/** + * Stream-based implementation of the RemoteCall interface. + * + * @author Ann Wollrath + */ +public class StreamRemoteCall implements RemoteCall { + private ConnectionInputStream in = null; + private ConnectionOutputStream out = null; + private Connection conn; + private boolean resultStarted = false; + private Exception serverException = null; + + public StreamRemoteCall(Connection c) { + conn = c; + } + + public StreamRemoteCall(Connection c, ObjID id, int op, long hash) + throws RemoteException + { + try { + conn = c; + Transport.transportLog.log(Log.VERBOSE, + "write remote call header..."); + + // write out remote call header info... + // call header, part 1 (read by Transport) + conn.getOutputStream().write(TransportConstants.Call); + getOutputStream(); // creates a MarshalOutputStream + id.write(out); // object id (target of call) + // call header, part 2 (read by Dispatcher) + out.writeInt(op); // method number (operation index) + out.writeLong(hash); // stub/skeleton hash + } catch (IOException e) { + throw new MarshalException("Error marshaling call header", e); + } + } + + /** + * Return the connection associated with this call. + */ + public Connection getConnection() { + return conn; + } + + /** + * Return the output stream the stub/skeleton should put arguments/results + * into. + */ + public ObjectOutput getOutputStream() throws IOException { + return getOutputStream(false); + } + + private ObjectOutput getOutputStream(boolean resultStream) + throws IOException + { + if (out == null) { + Transport.transportLog.log(Log.VERBOSE, "getting output stream"); + + out = new ConnectionOutputStream(conn, resultStream); + } + return out; + } + + /** + * Release the outputStream Currently, will not complain if the + * output stream is released more than once. + */ + public void releaseOutputStream() throws IOException { + try { + if (out != null) { + try { + out.flush(); + } finally { + out.done(); // always start DGC ack timer + } + } + conn.releaseOutputStream(); + } finally { + out = null; + } + } + + /** + * Get the InputStream the stub/skeleton should get results/arguments + * from. + */ + public ObjectInput getInputStream() throws IOException { + if (in == null) { + Transport.transportLog.log(Log.VERBOSE, "getting input stream"); + + in = new ConnectionInputStream(conn.getInputStream()); + } + return in; + } + + /** + * Release the input stream, this would allow some transports to release + * the channel early. + */ + public void releaseInputStream() throws IOException { + /* WARNING: Currently, the UnicastRef.java invoke methods rely + * upon this method not throwing an IOException. + */ + + try { + if (in != null) { + // execute MarshalInputStream "done" callbacks + try { + in.done(); + } catch (RuntimeException e) { + } + + // add saved references to DGC table + in.registerRefs(); + + /* WARNING: The connection being passed to done may have + * already been freed. + */ + in.done(conn); + } + conn.releaseInputStream(); + } finally { + in = null; + } + } + + /** + * Returns an output stream (may put out header information + * relating to the success of the call). + * @param success If true, indicates normal return, else indicates + * exceptional return. + * @exception StreamCorruptedException If result stream previously + * acquired + * @exception IOException For any other problem with I/O. + */ + public ObjectOutput getResultStream(boolean success) throws IOException { + /* make sure result code only marshaled once. */ + if (resultStarted) + throw new StreamCorruptedException("result already in progress"); + else + resultStarted = true; + + // write out return header + // return header, part 1 (read by Transport) + DataOutputStream wr = new DataOutputStream(conn.getOutputStream()); + wr.writeByte(TransportConstants.Return);// transport op + getOutputStream(true); // creates a MarshalOutputStream + // return header, part 2 (read by client-side RemoteCall) + if (success) // + out.writeByte(TransportConstants.NormalReturn); + else + out.writeByte(TransportConstants.ExceptionalReturn); + out.writeID(); // write id for gcAck + return out; + } + + /** + * Do whatever it takes to execute the call. + */ + public void executeCall() throws Exception { + byte returnType; + + // read result header + DGCAckHandler ackHandler = null; + try { + if (out != null) { + ackHandler = out.getDGCAckHandler(); + } + releaseOutputStream(); + DataInputStream rd = new DataInputStream(conn.getInputStream()); + byte op = rd.readByte(); + if (op != TransportConstants.Return) { + if (Transport.transportLog.isLoggable(Log.BRIEF)) { + Transport.transportLog.log(Log.BRIEF, + "transport return code invalid: " + op); + } + throw new UnmarshalException("Transport return code invalid"); + } + getInputStream(); + returnType = in.readByte(); + in.readID(); // id for DGC acknowledgement + } catch (UnmarshalException e) { + throw e; + } catch (IOException e) { + throw new UnmarshalException("Error unmarshaling return header", + e); + } finally { + if (ackHandler != null) { + ackHandler.release(); + } + } + + // read return value + switch (returnType) { + case TransportConstants.NormalReturn: + break; + + case TransportConstants.ExceptionalReturn: + Object ex; + try { + ex = in.readObject(); + } catch (Exception e) { + throw new UnmarshalException("Error unmarshaling return", e); + } + + // An exception should have been received, + // if so throw it, else flag error + if (ex instanceof Exception) { + exceptionReceivedFromServer((Exception) ex); + } else { + throw new UnmarshalException("Return type not Exception"); + } + default: + if (Transport.transportLog.isLoggable(Log.BRIEF)) { + Transport.transportLog.log(Log.BRIEF, + "return code invalid: " + returnType); + } + throw new UnmarshalException("Return code invalid"); + } + } + + /** + * Routine that causes the stack traces of remote exceptions to be + * filled in with the current stack trace on the client. Detail + * exceptions are filled in iteratively. + */ + protected void exceptionReceivedFromServer(Exception ex) throws Exception { + serverException = ex; + + StackTraceElement[] serverTrace = ex.getStackTrace(); + StackTraceElement[] clientTrace = (new Throwable()).getStackTrace(); + StackTraceElement[] combinedTrace = + new StackTraceElement[serverTrace.length + clientTrace.length]; + System.arraycopy(serverTrace, 0, combinedTrace, 0, + serverTrace.length); + System.arraycopy(clientTrace, 0, combinedTrace, serverTrace.length, + clientTrace.length); + ex.setStackTrace(combinedTrace); + + /* + * Log the details of a server exception thrown as a result of a + * remote method invocation. + */ + if (UnicastRef.clientCallLog.isLoggable(Log.BRIEF)) { + /* log call exception returned from server before it is rethrown */ + TCPEndpoint ep = (TCPEndpoint) conn.getChannel().getEndpoint(); + UnicastRef.clientCallLog.log(Log.BRIEF, "outbound call " + + "received exception: [" + ep.getHost() + ":" + + ep.getPort() + "] exception: ", ex); + } + + throw ex; + } + + /* + * method to retrieve possible server side exceptions (which will + * be throw from exceptionReceivedFromServer(...) ) + */ + public Exception getServerException() { + return serverException; + } + + public void done() throws IOException { + /* WARNING: Currently, the UnicastRef.java invoke methods rely + * upon this method not throwing an IOException. + */ + + releaseInputStream(); + } +} diff --git a/src/share/classes/sun/rmi/transport/Target.java b/src/share/classes/sun/rmi/transport/Target.java new file mode 100644 index 000000000..01df43800 --- /dev/null +++ b/src/share/classes/sun/rmi/transport/Target.java @@ -0,0 +1,471 @@ +/* + * Copyright 1996-2005 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ +package sun.rmi.transport; + +import java.rmi.Remote; +import java.rmi.NoSuchObjectException; +import java.rmi.dgc.VMID; +import java.rmi.server.ObjID; +import java.rmi.server.Unreferenced; +import java.security.AccessControlContext; +import java.security.AccessController; +import java.util.*; +import sun.rmi.runtime.Log; +import sun.rmi.runtime.NewThreadAction; +import sun.rmi.server.Dispatcher; + +/** + * A target contains information pertaining to a remote object that + * resides in this address space. Targets are located via the + * ObjectTable. + */ +public final class Target { + /** object id for target */ + private final ObjID id; + /** flag indicating whether target is subject to collection */ + private final boolean permanent; + /** weak reference to remote object implementation */ + private final WeakRef weakImpl; + /** dispatcher for remote object */ + private volatile Dispatcher disp; + /** stub for remote object */ + private final Remote stub; + /** set of clients that hold references to this target */ + private final Vector refSet = new Vector(); + /** table that maps client endpoints to sequence numbers */ + private final Hashtable sequenceTable = new Hashtable(5); + /** access control context in which target was created */ + private final AccessControlContext acc; + /** context class loader in which target was created */ + private final ClassLoader ccl; + /** number of pending/executing calls */ + private int callCount = 0; + /** true if this target has been removed from the object table */ + private boolean removed = false; + /** + * the transport through which this target was exported and + * through which remote calls will be allowed + */ + private volatile Transport exportedTransport = null; + + /** number to identify next callback thread created here */ + private static int nextThreadNum = 0; + + /** + * Construct a Target for a remote object "impl" with + * a specific object id. + * + * If "permanent" is true, then the impl is pinned permanently + * (the impl will not be collected via distributed and/or local + * GC). If "on" is false, than the impl is subject to + * collection. Permanent objects do not keep a server from + * exiting. + */ + public Target(Remote impl, Dispatcher disp, Remote stub, ObjID id, + boolean permanent) + { + this.weakImpl = new WeakRef(impl, ObjectTable.reapQueue); + this.disp = disp; + this.stub = stub; + this.id = id; + this.acc = AccessController.getContext(); + + /* + * Fix for 4149366: so that downloaded parameter types unmarshalled + * for this impl will be compatible with types known only to the + * impl class's class loader (when it's not identical to the + * exporting thread's context class loader), mark the impl's class + * loader as the loader to use as the context class loader in the + * server's dispatch thread while a call to this impl is being + * processed (unless this exporting thread's context class loader is + * a child of the impl's class loader, such as when a registry is + * exported by an application, in which case this thread's context + * class loader is preferred). + */ + ClassLoader threadContextLoader = + Thread.currentThread().getContextClassLoader(); + ClassLoader serverLoader = impl.getClass().getClassLoader(); + if (checkLoaderAncestry(threadContextLoader, serverLoader)) { + this.ccl = threadContextLoader; + } else { + this.ccl = serverLoader; + } + + this.permanent = permanent; + if (permanent) { + pinImpl(); + } + } + + /** + * Return true if the first class loader is a child of (or identical + * to) the second class loader. Either loader may be "null", which is + * considered to be the parent of any non-null class loader. + * + * (utility method added for the 1.2beta4 fix for 4149366) + */ + private static boolean checkLoaderAncestry(ClassLoader child, + ClassLoader ancestor) + { + if (ancestor == null) { + return true; + } else if (child == null) { + return false; + } else { + for (ClassLoader parent = child; + parent != null; + parent = parent.getParent()) + { + if (parent == ancestor) { + return true; + } + } + return false; + } + } + + /** Get the stub (proxy) object for this target + */ + public Remote getStub() { + return stub; + } + + /** + * Returns the object endpoint for the target. + */ + ObjectEndpoint getObjectEndpoint() { + return new ObjectEndpoint(id, exportedTransport); + } + + /** + * Get the weak reference for the Impl of this target. + */ + WeakRef getWeakImpl() { + return weakImpl; + } + + /** + * Returns the dispatcher for this remote object target. + */ + Dispatcher getDispatcher() { + return disp; + } + + AccessControlContext getAccessControlContext() { + return acc; + } + + ClassLoader getContextClassLoader() { + return ccl; + } + + /** + * Get the impl for this target. + * Note: this may return null if the impl has been garbage collected. + * (currently, there is no need to make this method public) + */ + Remote getImpl() { + return (Remote)weakImpl.get(); + } + + /** + * Returns true if the target is permanent. + */ + boolean isPermanent() { + return permanent; + } + + /** + * Pin impl in target. Pin the WeakRef object so it holds a strong + * reference to the object to it will not be garbage collected locally. + * This way there is a single object responsible for the weak ref + * mechanism. + */ + synchronized void pinImpl() { + weakImpl.pin(); + } + + /** + * Unpin impl in target. Weaken the reference to impl so that it + * can be garbage collected locally. But only if there the refSet + * is empty. All of the weak/strong handling is in WeakRef + */ + synchronized void unpinImpl() { + /* only unpin if: + * a) impl is not permanent, and + * b) impl is not already unpinned, and + * c) there are no external references (outside this + * address space) for the impl + */ + if (!permanent && refSet.isEmpty()) { + weakImpl.unpin(); + } + } + + /** + * Enable the transport through which remote calls to this target + * are allowed to be set if it has not already been set. + */ + void setExportedTransport(Transport exportedTransport) { + if (this.exportedTransport == null) { + this.exportedTransport = exportedTransport; + } + } + + /** + * Add an endpoint to the remembered set. Also adds a notifier + * to call back if the address space associated with the endpoint + * dies. + */ + synchronized void referenced(long sequenceNum, VMID vmid) { + // check sequence number for vmid + SequenceEntry entry = (SequenceEntry) sequenceTable.get(vmid); + if (entry == null) { + sequenceTable.put(vmid, new SequenceEntry(sequenceNum)); + } else if (entry.sequenceNum < sequenceNum) { + entry.update(sequenceNum); + } else { + // late dirty call; ignore. + return; + } + + if (!refSet.contains(vmid)) { + /* + * A Target must be pinned while its refSet is not empty. It may + * have become unpinned if external LiveRefs only existed in + * serialized form for some period of time, or if a client failed + * to renew its lease due to a transient network failure. So, + * make sure that it is pinned here; this fixes bugid 4069644. + */ + pinImpl(); + if (getImpl() == null) // too late if impl was collected + return; + + if (DGCImpl.dgcLog.isLoggable(Log.VERBOSE)) { + DGCImpl.dgcLog.log(Log.VERBOSE, "add to dirty set: " + vmid); + } + + refSet.addElement(vmid); + + DGCImpl.getDGCImpl().registerTarget(vmid, this); + } + } + + /** + * Remove endpoint from remembered set. If set becomes empty, + * remove server from Transport's object table. + */ + synchronized void unreferenced(long sequenceNum, VMID vmid, boolean strong) + { + // check sequence number for vmid + SequenceEntry entry = (SequenceEntry) sequenceTable.get(vmid); + if (entry == null || entry.sequenceNum > sequenceNum) { + // late clean call; ignore + return; + } else if (strong) { + // strong clean call; retain sequenceNum + entry.retain(sequenceNum); + } else if (entry.keep == false) { + // get rid of sequence number + sequenceTable.remove(vmid); + } + + if (DGCImpl.dgcLog.isLoggable(Log.VERBOSE)) { + DGCImpl.dgcLog.log(Log.VERBOSE, "remove from dirty set: " + vmid); + } + + refSetRemove(vmid); + } + + /** + * Remove endpoint from the reference set. + */ + synchronized private void refSetRemove(VMID vmid) { + // remove notification request + DGCImpl.getDGCImpl().unregisterTarget(vmid, this); + + if (refSet.removeElement(vmid) && refSet.isEmpty()) { + // reference set is empty, so server can be garbage collected. + // remove object from table. + if (DGCImpl.dgcLog.isLoggable(Log.VERBOSE)) { + DGCImpl.dgcLog.log(Log.VERBOSE, + "reference set is empty: target = " + this); + } + + /* + * If the remote object implements the Unreferenced interface, + * invoke its unreferenced callback in a separate thread. + */ + Remote obj = getImpl(); + if (obj instanceof Unreferenced) { + final Unreferenced unrefObj = (Unreferenced) obj; + final Thread t = (Thread) + java.security.AccessController.doPrivileged( + new NewThreadAction(new Runnable() { + public void run() { + unrefObj.unreferenced(); + } + }, "Unreferenced-" + nextThreadNum++, false, true)); + // REMIND: access to nextThreadNum not synchronized; you care? + /* + * We must manually set the context class loader appropriately + * for threads that may invoke user code (see bugid 4171278). + */ + java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + t.setContextClassLoader(ccl); + return null; + } + }); + + t.start(); + } + + unpinImpl(); + } + } + + /** + * Mark this target as not accepting new calls if any of the + * following conditions exist: a) the force parameter is true, + * b) the target's call count is zero, or c) the object is already + * not accepting calls. Returns true if target is marked as not + * accepting new calls; returns false otherwise. + */ + synchronized boolean unexport(boolean force) { + + if ((force == true) || (callCount == 0) || (disp == null)) { + disp = null; + /* + * Fix for 4331349: unpin object so that it may be gc'd. + * Also, unregister all vmids referencing this target + * so target can be gc'd. + */ + unpinImpl(); + DGCImpl dgc = DGCImpl.getDGCImpl(); + Enumeration enum_ = refSet.elements(); + while (enum_.hasMoreElements()) { + VMID vmid = (VMID) enum_.nextElement(); + dgc.unregisterTarget(vmid, this); + } + return true; + } else { + return false; + } + } + + /** + * Mark this target as having been removed from the object table. + */ + synchronized void markRemoved() { + if (!(!removed)) { throw new AssertionError(); } + + removed = true; + if (!permanent && callCount == 0) { + ObjectTable.decrementKeepAliveCount(); + } + + if (exportedTransport != null) { + exportedTransport.targetUnexported(); + } + } + + /** + * Increment call count. + */ + synchronized void incrementCallCount() throws NoSuchObjectException { + + if (disp != null) { + callCount ++; + } else { + throw new NoSuchObjectException("object not accepting new calls"); + } + } + + /** + * Decrement call count. + */ + synchronized void decrementCallCount() { + + if (--callCount < 0) { + throw new Error("internal error: call count less than zero"); + } + + /* + * The "keep-alive count" is the number of non-permanent remote + * objects that are either in the object table or still have calls + * in progress. Therefore, this state change may affect the + * keep-alive count: if this target is for a non-permanent remote + * object that has been removed from the object table and now has a + * call count of zero, it needs to be decremented. + */ + if (!permanent && removed && callCount == 0) { + ObjectTable.decrementKeepAliveCount(); + } + } + + /** + * Returns true if remembered set is empty; otherwise returns + * false + */ + boolean isEmpty() { + return refSet.isEmpty(); + } + + /** + * This method is called if the address space associated with the + * vmid dies. In that case, the vmid should be removed + * from the reference set. + */ + synchronized public void vmidDead(VMID vmid) { + if (DGCImpl.dgcLog.isLoggable(Log.BRIEF)) { + DGCImpl.dgcLog.log(Log.BRIEF, "removing endpoint " + + vmid + " from reference set"); + } + + sequenceTable.remove(vmid); + refSetRemove(vmid); + } +} + +class SequenceEntry { + long sequenceNum; + boolean keep; + + SequenceEntry(long sequenceNum) { + this.sequenceNum = sequenceNum; + keep = false; + } + + void retain(long sequenceNum) { + this.sequenceNum = sequenceNum; + keep = true; + } + + void update(long sequenceNum) { + this.sequenceNum = sequenceNum; + } +} diff --git a/src/share/classes/sun/rmi/transport/Transport.java b/src/share/classes/sun/rmi/transport/Transport.java new file mode 100644 index 000000000..572a55b2b --- /dev/null +++ b/src/share/classes/sun/rmi/transport/Transport.java @@ -0,0 +1,235 @@ +/* + * Copyright 1996-2005 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.transport; + +import java.io.IOException; +import java.io.ObjectOutput; +import java.rmi.MarshalException; +import java.rmi.NoSuchObjectException; +import java.rmi.Remote; +import java.rmi.RemoteException; +import java.rmi.server.LogStream; +import java.rmi.server.ObjID; +import java.rmi.server.RemoteCall; +import java.rmi.server.RemoteServer; +import java.rmi.server.ServerNotActiveException; +import java.security.AccessControlContext; +import sun.rmi.runtime.Log; +import sun.rmi.server.Dispatcher; +import sun.rmi.server.UnicastServerRef; + +/** + * Transport abstraction for enabling communication between different + * VMs. + * + * @author Ann Wollrath + */ +public abstract class Transport { + + /** "transport" package log level */ + static final int logLevel = LogStream.parseLevel(getLogLevel()); + + private static String getLogLevel() { + return (String) java.security.AccessController.doPrivileged( + new sun.security.action.GetPropertyAction("sun.rmi.transport.logLevel")); + } + + /* transport package log */ + static final Log transportLog = + Log.getLog("sun.rmi.transport.misc", "transport", Transport.logLevel); + + /** References the current transport when a call is being serviced */ + private static final ThreadLocal currentTransport = new ThreadLocal(); + + /** ObjID for DGCImpl */ + private static final ObjID dgcID = new ObjID(ObjID.DGC_ID); + + /** + * Returns a <I>Channel</I> that generates connections to the + * endpoint <I>ep</I>. A Channel is an object that creates and + * manages connections of a particular type to some particular + * address space. + * @param ep the endpoint to which connections will be generated. + * @return the channel or null if the transport cannot + * generate connections to this endpoint + */ + public abstract Channel getChannel(Endpoint ep); + + /** + * Removes the <I>Channel</I> that generates connections to the + * endpoint <I>ep</I>. + */ + public abstract void free(Endpoint ep); + + /** + * Export the object so that it can accept incoming calls. + */ + public void exportObject(Target target) throws RemoteException { + target.setExportedTransport(this); + ObjectTable.putTarget(target); + } + + /** + * Invoked when an object that was exported on this transport has + * become unexported, either by being garbage collected or by + * being explicitly unexported. + **/ + protected void targetUnexported() { } + + /** + * Returns the current transport if a call is being serviced, otherwise + * returns null. + **/ + static Transport currentTransport() { + return (Transport) currentTransport.get(); + } + + /** + * Verify that the current access control context has permission to accept + * the connection being dispatched by the current thread. The current + * access control context is passed as a parameter to avoid the overhead of + * an additional call to AccessController.getContext. + */ + protected abstract void checkAcceptPermission(AccessControlContext acc); + + /** + * Service an incoming remote call. When a message arrives on the + * connection indicating the beginning of a remote call, the + * threads are required to call the <I>serviceCall</I> method of + * their transport. The default implementation of this method + * locates and calls the dispatcher object. Ordinarily a + * transport implementation will not need to override this method. + * At the entry to <I>tr.serviceCall(conn)</I>, the connection's + * input stream is positioned at the start of the incoming + * message. The <I>serviceCall</I> method processes the incoming + * remote invocation and sends the result on the connection's + * output stream. If it returns "true", then the remote + * invocation was processed without error and the transport can + * cache the connection. If it returns "false", a protocol error + * occurred during the call, and the transport should destroy the + * connection. + */ + public boolean serviceCall(final RemoteCall call) { + try { + /* read object id */ + final Remote impl; + ObjID id; + + try { + id = ObjID.read(call.getInputStream()); + } catch (java.io.IOException e) { + throw new MarshalException("unable to read objID", e); + } + + /* get the remote object */ + Transport transport = id.equals(dgcID) ? null : this; + Target target = + ObjectTable.getTarget(new ObjectEndpoint(id, transport)); + + if (target == null || (impl = target.getImpl()) == null) { + throw new NoSuchObjectException("no such object in table"); + } + + final Dispatcher disp = target.getDispatcher(); + target.incrementCallCount(); + try { + /* call the dispatcher */ + transportLog.log(Log.VERBOSE, "call dispatcher"); + + final AccessControlContext acc = + target.getAccessControlContext(); + ClassLoader ccl = target.getContextClassLoader(); + + Thread t = Thread.currentThread(); + ClassLoader savedCcl = t.getContextClassLoader(); + + try { + t.setContextClassLoader(ccl); + currentTransport.set(this); + try { + java.security.AccessController.doPrivileged( + new java.security.PrivilegedExceptionAction() { + public Object run() throws IOException { + checkAcceptPermission(acc); + disp.dispatch(impl, call); + return null; + } + }, acc); + } catch (java.security.PrivilegedActionException pae) { + throw (IOException) pae.getException(); + } + } finally { + t.setContextClassLoader(savedCcl); + currentTransport.set(null); + } + + } catch (IOException ex) { + transportLog.log(Log.BRIEF, + "exception thrown by dispatcher: ", ex); + return false; + } finally { + target.decrementCallCount(); + } + + } catch (RemoteException e) { + + // if calls are being logged, write out exception + if (UnicastServerRef.callLog.isLoggable(Log.BRIEF)) { + // include client host name if possible + String clientHost = ""; + try { + clientHost = "[" + + RemoteServer.getClientHost() + "] "; + } catch (ServerNotActiveException ex) { + } + String message = clientHost + "exception: "; + UnicastServerRef.callLog.log(Log.BRIEF, message, e); + } + + /* We will get a RemoteException if either a) the objID is + * not readable, b) the target is not in the object table, or + * c) the object is in the midst of being unexported (note: + * NoSuchObjectException is thrown by the incrementCallCount + * method if the object is being unexported). Here it is + * relatively safe to marshal an exception to the client + * since the client will not have seen a return value yet. + */ + try { + ObjectOutput out = call.getResultStream(false); + UnicastServerRef.clearStackTraces(e); + out.writeObject(e); + call.releaseOutputStream(); + + } catch (IOException ie) { + transportLog.log(Log.BRIEF, + "exception thrown marshalling exception: ", ie); + return false; + } + } + + return true; + } +} diff --git a/src/share/classes/sun/rmi/transport/TransportConstants.java b/src/share/classes/sun/rmi/transport/TransportConstants.java new file mode 100644 index 000000000..534baf873 --- /dev/null +++ b/src/share/classes/sun/rmi/transport/TransportConstants.java @@ -0,0 +1,61 @@ +/* + * Copyright 1996 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.transport; + +public class TransportConstants { + /** Transport magic number: "JRMI"*/ + public static final int Magic = 0x4a524d49; + /** Transport version number */ + public static final short Version = 2; + + /** Connection uses stream protocol */ + public static final byte StreamProtocol = 0x4b; + /** Protocol for single operation per connection; no ack required */ + public static final byte SingleOpProtocol = 0x4c; + /** Connection uses multiplex protocol */ + public static final byte MultiplexProtocol = 0x4d; + + /** Ack for transport protocol */ + public static final byte ProtocolAck = 0x4e; + /** Negative ack for transport protocol (protocol not supported) */ + public static final byte ProtocolNack = 0x4f; + + /** RMI call */ + public static final byte Call = 0x50; + /** RMI return */ + public static final byte Return = 0x51; + /** Ping operation */ + public static final byte Ping = 0x52; + /** Acknowledgment for Ping operation */ + public static final byte PingAck = 0x53; + /** Acknowledgment for distributed GC */ + public static final byte DGCAck = 0x54; + + /** Normal return (with or without return value) */ + public static final byte NormalReturn = 0x01; + /** Exceptional return */ + public static final byte ExceptionalReturn = 0x02; +} diff --git a/src/share/classes/sun/rmi/transport/WeakRef.java b/src/share/classes/sun/rmi/transport/WeakRef.java new file mode 100644 index 000000000..08b2519ed --- /dev/null +++ b/src/share/classes/sun/rmi/transport/WeakRef.java @@ -0,0 +1,139 @@ +/* + * Copyright 1996-2001 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ +package sun.rmi.transport; + +import java.lang.ref.*; +import sun.rmi.runtime.Log; + +/** + * WeakRef objects are used by the RMI runtime to hold potentially weak + * references to exported remote objects in the local object table. + * + * This class extends the functionality of java.lang.ref.WeakReference in + * several ways. The methods pin() and unpin() can be used to set + * whether the contained reference is strong or weak (it is weak upon + * construction). The hashCode() and equals() methods are overridden so + * that WeakRef objects hash and compare to each other according to the + * object identity of their referents. + * + * @author Ann Wollrath + * @author Peter Jones + */ +class WeakRef extends WeakReference { + + /** value of the referent's "identity" hash code */ + private int hashValue; + + /** strong reference to the referent, for when this WeakRef is "pinned" */ + private Object strongRef = null; + + /** + * Create a new WeakRef to the given object. + */ + public WeakRef(Object obj) { + super(obj); + setHashValue(obj); // cache object's "identity" hash code + } + + /** + * Create a new WeakRef to the given object, registered with a queue. + */ + public WeakRef(Object obj, ReferenceQueue q) { + super(obj, q); + setHashValue(obj); // cache object's "identity" hash code + } + + /** + * Pin the contained reference (make this a strong reference). + */ + public synchronized void pin() { + if (strongRef == null) { + strongRef = get(); + + if (DGCImpl.dgcLog.isLoggable(Log.VERBOSE)) { + DGCImpl.dgcLog.log(Log.VERBOSE, + "strongRef = " + strongRef); + } + } + } + + /** + * Unpin the contained reference (make this a weak reference). + */ + public synchronized void unpin() { + if (strongRef != null) { + if (DGCImpl.dgcLog.isLoggable(Log.VERBOSE)) { + DGCImpl.dgcLog.log(Log.VERBOSE, + "strongRef = " + strongRef); + } + + strongRef = null; + } + } + + /* + * Cache referent's "identity" hash code (so that we still have the + * value after the referent gets cleared). + * + * We cannot use the value from the object's hashCode() method, since + * if the object is of a remote class not extended from RemoteObject + * and it is trying to implement hashCode() and equals() so that it + * can be compared to stub objects, its own hash code could not have + * been initialized yet (see bugid 4102938). Also, object table keys + * based on server objects are indeed matched on object identity, so + * this is the correct hash technique regardless. + */ + private void setHashValue(Object obj) { + if (obj != null) { + hashValue = System.identityHashCode(obj); + } else { + hashValue = 0; + } + } + + /** + * Always return the "identity" hash code of the original referent. + */ + public int hashCode() { + return hashValue; + } + + /** + * Return true if "obj" is this identical WeakRef object, or, if the + * contained reference has not been cleared, if "obj" is another WeakRef + * object with the identical non-null referent. Otherwise, return false. + */ + public boolean equals(Object obj) { + if (obj instanceof WeakRef) { + if (obj == this) + return true; + + Object referent = get(); + return (referent != null) && (referent == ((WeakRef) obj).get()); + } else { + return false; + } + } +} diff --git a/src/share/classes/sun/rmi/transport/proxy/CGIHandler.java b/src/share/classes/sun/rmi/transport/proxy/CGIHandler.java new file mode 100644 index 000000000..3d91ab0d1 --- /dev/null +++ b/src/share/classes/sun/rmi/transport/proxy/CGIHandler.java @@ -0,0 +1,401 @@ +/* + * Copyright 1996-1998 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ +package sun.rmi.transport.proxy; + +import java.io.*; +import java.net.*; +import java.util.Hashtable; + +/** + * CGIClientException is thrown when an error is detected + * in a client's request. + */ +class CGIClientException extends Exception { + + public CGIClientException(String s) { + super(s); + } +} + +/** + * CGIServerException is thrown when an error occurs here on the server. + */ +class CGIServerException extends Exception { + + public CGIServerException(String s) { + super(s); + } +} + +/** + * CGICommandHandler is the interface to an object that handles a + * particular supported command. + */ +interface CGICommandHandler { + + /** + * Return the string form of the command + * to be recognized in the query string. + */ + public String getName(); + + /** + * Execute the command with the given string as parameter. + */ + public void execute(String param) throws CGIClientException, CGIServerException; +} + +/** + * The CGIHandler class contains methods for executing as a CGI program. + * The main function interprets the query string as a command of the form + * "<command>=<parameters>". + * + * This class depends on the CGI 1.0 environment variables being set as + * properties of the same name in this Java VM. + * + * All data and methods of this class are static because they are specific + * to this particular CGI process. + */ +public final class CGIHandler { + + /* get CGI parameters that we need */ + static int ContentLength; + static String QueryString; + static String RequestMethod; + static String ServerName; + static int ServerPort; + + static { + java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + ContentLength = + Integer.getInteger("CONTENT_LENGTH", 0).intValue(); + QueryString = System.getProperty("QUERY_STRING", ""); + RequestMethod = System.getProperty("REQUEST_METHOD", ""); + ServerName = System.getProperty("SERVER_NAME", ""); + ServerPort = Integer.getInteger("SERVER_PORT", 0).intValue(); + return null; + } + }); + } + + /* list of handlers for supported commands */ + private static CGICommandHandler commands[] = { + new CGIForwardCommand(), + new CGIGethostnameCommand(), + new CGIPingCommand(), + new CGITryHostnameCommand() + }; + + /* construct table mapping command strings to handlers */ + private static Hashtable commandLookup; + static { + commandLookup = new Hashtable(); + for (int i = 0; i < commands.length; ++ i) + commandLookup.put(commands[i].getName(), commands[i]); + } + + /* prevent instantiation of this class */ + private CGIHandler() {} + + /** + * Execute command given in query string on URL. The string before + * the first '=' is interpreted as the command name, and the string + * after the first '=' is the parameters to the command. + */ + public static void main(String args[]) + { + try { + String command, param; + int delim = QueryString.indexOf("="); + if (delim == -1) { + command = QueryString; + param = ""; + } + else { + command = QueryString.substring(0, delim); + param = QueryString.substring(delim + 1); + } + CGICommandHandler handler = + (CGICommandHandler) commandLookup.get(command); + if (handler != null) + try { + handler.execute(param); + } catch (CGIClientException e) { + returnClientError(e.getMessage()); + } catch (CGIServerException e) { + returnServerError(e.getMessage()); + } + else + returnClientError("invalid command: " + command); + } catch (Exception e) { + returnServerError("internal error: " + e.getMessage()); + } + System.exit(0); + } + + /** + * Return an HTML error message indicating there was error in + * the client's request. + */ + private static void returnClientError(String message) + { + System.out.println("Status: 400 Bad Request: " + message); + System.out.println("Content-type: text/html"); + System.out.println(""); + System.out.println("<HTML>" + + "<HEAD><TITLE>Java RMI Client Error" + + "</TITLE></HEAD>" + + "<BODY>"); + System.out.println("<H1>Java RMI Client Error</H1>"); + System.out.println(""); + System.out.println(message); + System.out.println("</BODY></HTML>"); + System.exit(1); + } + + /** + * Return an HTML error message indicating an error occurred + * here on the server. + */ + private static void returnServerError(String message) + { + System.out.println("Status: 500 Server Error: " + message); + System.out.println("Content-type: text/html"); + System.out.println(""); + System.out.println("<HTML>" + + "<HEAD><TITLE>Java RMI Server Error" + + "</TITLE></HEAD>" + + "<BODY>"); + System.out.println("<H1>Java RMI Server Error</H1>"); + System.out.println(""); + System.out.println(message); + System.out.println("</BODY></HTML>"); + System.exit(1); + } +} + +/** + * "forward" command: Forward request body to local port on the server, + * and send reponse back to client. + */ +final class CGIForwardCommand implements CGICommandHandler { + + public String getName() { + return "forward"; + } + + public void execute(String param) throws CGIClientException, CGIServerException + { + if (!CGIHandler.RequestMethod.equals("POST")) + throw new CGIClientException("can only forward POST requests"); + + int port; + try { + port = Integer.parseInt(param); + } catch (NumberFormatException e) { + throw new CGIClientException("invalid port number: " + param); + } + if (port <= 0 || port > 0xFFFF) + throw new CGIClientException("invalid port: " + port); + if (port < 1024) + throw new CGIClientException("permission denied for port: " + + port); + + byte buffer[]; + Socket socket; + try { + socket = new Socket(InetAddress.getLocalHost(), port); + } catch (IOException e) { + throw new CGIServerException("could not connect to local port"); + } + + /* + * read client's request body + */ + DataInputStream clientIn = new DataInputStream(System.in); + buffer = new byte[CGIHandler.ContentLength]; + try { + clientIn.readFully(buffer); + } catch (EOFException e) { + throw new CGIClientException("unexpected EOF reading request body"); + } catch (IOException e) { + throw new CGIClientException("error reading request body"); + } + + /* + * send to local server in HTTP + */ + try { + DataOutputStream socketOut = + new DataOutputStream(socket.getOutputStream()); + socketOut.writeBytes("POST / HTTP/1.0\r\n"); + socketOut.writeBytes("Content-length: " + + CGIHandler.ContentLength + "\r\n\r\n"); + socketOut.write(buffer); + socketOut.flush(); + } catch (IOException e) { + throw new CGIServerException("error writing to server"); + } + + /* + * read response + */ + DataInputStream socketIn; + try { + socketIn = new DataInputStream(socket.getInputStream()); + } catch (IOException e) { + throw new CGIServerException("error reading from server"); + } + String key = "Content-length:".toLowerCase(); + boolean contentLengthFound = false; + String line; + int responseContentLength = -1; + do { + try { + line = socketIn.readLine(); + } catch (IOException e) { + throw new CGIServerException("error reading from server"); + } + if (line == null) + throw new CGIServerException( + "unexpected EOF reading server response"); + + if (line.toLowerCase().startsWith(key)) { + if (contentLengthFound) + ; // what would we want to do in this case?? + responseContentLength = + Integer.parseInt(line.substring(key.length()).trim()); + contentLengthFound = true; + } + } while ((line.length() != 0) && + (line.charAt(0) != '\r') && (line.charAt(0) != '\n')); + + if (!contentLengthFound || responseContentLength < 0) + throw new CGIServerException( + "missing or invalid content length in server response"); + buffer = new byte[responseContentLength]; + try { + socketIn.readFully(buffer); + } catch (EOFException e) { + throw new CGIServerException( + "unexpected EOF reading server response"); + } catch (IOException e) { + throw new CGIServerException("error reading from server"); + } + + /* + * send response back to client + */ + System.out.println("Status: 200 OK"); + System.out.println("Content-type: application/octet-stream"); + System.out.println(""); + try { + System.out.write(buffer); + } catch (IOException e) { + throw new CGIServerException("error writing response"); + } + System.out.flush(); + } +} + +/** + * "gethostname" command: Return the host name of the server as the + * response body + */ +final class CGIGethostnameCommand implements CGICommandHandler { + + public String getName() { + return "gethostname"; + } + + public void execute(String param) + { + System.out.println("Status: 200 OK"); + System.out.println("Content-type: application/octet-stream"); + System.out.println("Content-length: " + + CGIHandler.ServerName.length()); + System.out.println(""); + System.out.print(CGIHandler.ServerName); + System.out.flush(); + } +} + +/** + * "ping" command: Return an OK status to indicate that connection + * was successful. + */ +final class CGIPingCommand implements CGICommandHandler { + + public String getName() { + return "ping"; + } + + public void execute(String param) + { + System.out.println("Status: 200 OK"); + System.out.println("Content-type: application/octet-stream"); + System.out.println("Content-length: 0"); + System.out.println(""); + } +} + +/** + * "tryhostname" command: Return a human readable message describing + * what host name is available to local Java VMs. + */ +final class CGITryHostnameCommand implements CGICommandHandler { + + public String getName() { + return "tryhostname"; + } + + public void execute(String param) + { + System.out.println("Status: 200 OK"); + System.out.println("Content-type: text/html"); + System.out.println(""); + System.out.println("<HTML>" + + "<HEAD><TITLE>Java RMI Server Hostname Info" + + "</TITLE></HEAD>" + + "<BODY>"); + System.out.println("<H1>Java RMI Server Hostname Info</H1>"); + System.out.println("<H2>Local host name available to Java VM:</H2>"); + System.out.print("<P>InetAddress.getLocalHost().getHostName()"); + try { + String localHostName = InetAddress.getLocalHost().getHostName(); + + System.out.println(" = " + localHostName); + } catch (UnknownHostException e) { + System.out.println(" threw java.net.UnknownHostException"); + } + + System.out.println("<H2>Server host information obtained through CGI interface from HTTP server:</H2>"); + System.out.println("<P>SERVER_NAME = " + CGIHandler.ServerName); + System.out.println("<P>SERVER_PORT = " + CGIHandler.ServerPort); + System.out.println("</BODY></HTML>"); + } +} diff --git a/src/share/classes/sun/rmi/transport/proxy/HttpAwareServerSocket.java b/src/share/classes/sun/rmi/transport/proxy/HttpAwareServerSocket.java new file mode 100644 index 000000000..6c168c57a --- /dev/null +++ b/src/share/classes/sun/rmi/transport/proxy/HttpAwareServerSocket.java @@ -0,0 +1,114 @@ +/* + * Copyright 1996-2005 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ +package sun.rmi.transport.proxy; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import sun.rmi.runtime.Log; + +/** + * The HttpAwareServerSocket class extends the java.net.ServerSocket + * class. It behaves like a ServerSocket, except that if + * the first four bytes of an accepted socket are the letters "POST", + * then it returns an HttpReceiveSocket instead of a java.net.Socket. + * This means that the accept method blocks until four bytes have been + * read from the new socket's input stream. + */ +class HttpAwareServerSocket extends ServerSocket { + + /** + * Create a server socket on a specified port. + * @param port the port + * @exception IOException IO error when opening the socket. + */ + public HttpAwareServerSocket(int port) throws IOException + { + super(port); + } + + /** + * Create a server socket, bind it to the specified local port + * and listen to it. You can connect to an annonymous port by + * specifying the port number to be 0. <i>backlog</i> specifies + * how many connection requests the system will queue up while waiting + * for the ServerSocket to execute accept(). + * @param port the specified port + * @param backlog the number of queued connect requests pending accept + */ + public HttpAwareServerSocket(int port, int backlog) throws IOException + { + super(port, backlog); + } + + /** + * Accept a connection. This method will block until the connection + * is made and four bytes can be read from the input stream. + * If the first four bytes are "POST", then an HttpReceiveSocket is + * returned, which will handle the HTTP protocol wrapping. + * Otherwise, a WrappedSocket is returned. The input stream will be + * reset to the beginning of the transmission. + * In either case, a BufferedInputStream will already be on top of + * the underlying socket's input stream. + * @exception IOException IO error when waiting for the connection. + */ + public Socket accept() throws IOException + { + Socket socket = super.accept(); + BufferedInputStream in = + new BufferedInputStream(socket.getInputStream()); + + RMIMasterSocketFactory.proxyLog.log(Log.BRIEF, + "socket accepted (checking for POST)"); + + in.mark(4); + boolean isHttp = (in.read() == 'P') && + (in.read() == 'O') && + (in.read() == 'S') && + (in.read() == 'T'); + in.reset(); + + if (RMIMasterSocketFactory.proxyLog.isLoggable(Log.BRIEF)) { + RMIMasterSocketFactory.proxyLog.log(Log.BRIEF, + (isHttp ? "POST found, HTTP socket returned" : + "POST not found, direct socket returned")); + } + + if (isHttp) + return new HttpReceiveSocket(socket, in, null); + else + return new WrappedSocket(socket, in, null); + } + + /** + * Return the implementation address and implementation port of + * the HttpAwareServerSocket as a String. + */ + public String toString() + { + return "HttpAware" + super.toString(); + } +} diff --git a/src/share/classes/sun/rmi/transport/proxy/HttpInputStream.java b/src/share/classes/sun/rmi/transport/proxy/HttpInputStream.java new file mode 100644 index 000000000..229a2cebd --- /dev/null +++ b/src/share/classes/sun/rmi/transport/proxy/HttpInputStream.java @@ -0,0 +1,201 @@ +/* + * Copyright 1996-2001 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ +package sun.rmi.transport.proxy; + +import java.io.*; + +import sun.rmi.runtime.Log; + +/** + * The HttpInputStream class assists the HttpSendSocket and HttpReceiveSocket + * classes by filtering out the header for the message as well as any + * data after its proper content length. + */ +class HttpInputStream extends FilterInputStream { + + /** bytes remaining to be read from proper content of message */ + protected int bytesLeft; + + /** bytes remaining to be read at time of last mark */ + protected int bytesLeftAtMark; + + /** + * Create new filter on a given input stream. + * @param in the InputStream to filter from + */ + public HttpInputStream(InputStream in) throws IOException + { + super(in); + + if (in.markSupported()) + in.mark(0); // prevent resetting back to old marks + + // pull out header, looking for content length + + DataInputStream dis = new DataInputStream(in); + String key = "Content-length:".toLowerCase(); + boolean contentLengthFound = false; + String line; + do { + line = dis.readLine(); + + if (RMIMasterSocketFactory.proxyLog.isLoggable(Log.VERBOSE)) { + RMIMasterSocketFactory.proxyLog.log(Log.VERBOSE, + "received header line: \"" + line + "\""); + } + + if (line == null) + throw new EOFException(); + + if (line.toLowerCase().startsWith(key)) { + if (contentLengthFound) + ; // what would we want to do in this case?? + bytesLeft = + Integer.parseInt(line.substring(key.length()).trim()); + contentLengthFound = true; + } + + // The idea here is to go past the first blank line. + // Some DataInputStream.readLine() documentation specifies that + // it does include the line-terminating character(s) in the + // returned string, but it actually doesn't, so we'll cover + // all cases here... + } while ((line.length() != 0) && + (line.charAt(0) != '\r') && (line.charAt(0) != '\n')); + + if (!contentLengthFound || bytesLeft < 0) { + // This really shouldn't happen, but if it does, shoud we fail?? + // For now, just give up and let a whole lot of bytes through... + bytesLeft = Integer.MAX_VALUE; + } + bytesLeftAtMark = bytesLeft; + + if (RMIMasterSocketFactory.proxyLog.isLoggable(Log.VERBOSE)) { + RMIMasterSocketFactory.proxyLog.log(Log.VERBOSE, + "content length: " + bytesLeft); + } + } + + /** + * Returns the number of bytes that can be read with blocking. + * Make sure that this does not exceed the number of bytes remaining + * in the proper content of the message. + */ + public int available() throws IOException + { + int bytesAvailable = in.available(); + if (bytesAvailable > bytesLeft) + bytesAvailable = bytesLeft; + + return bytesAvailable; + } + + /** + * Read a byte of data from the stream. Make sure that one is available + * from the proper content of the message, else -1 is returned to + * indicate to the user that the end of the stream has been reached. + */ + public int read() throws IOException + { + if (bytesLeft > 0) { + int data = in.read(); + if (data != -1) + -- bytesLeft; + + if (RMIMasterSocketFactory.proxyLog.isLoggable(Log.VERBOSE)) { + RMIMasterSocketFactory.proxyLog.log(Log.VERBOSE, + "received byte: '" + + ((data & 0x7F) < ' ' ? " " : String.valueOf((char) data)) + + "' " + data); + } + + return data; + } + else { + RMIMasterSocketFactory.proxyLog.log(Log.VERBOSE, + "read past content length"); + + return -1; + } + } + + public int read(byte b[], int off, int len) throws IOException + { + if (bytesLeft == 0 && len > 0) { + RMIMasterSocketFactory.proxyLog.log(Log.VERBOSE, + "read past content length"); + + return -1; + } + if (len > bytesLeft) + len = bytesLeft; + int bytesRead = in.read(b, off, len); + bytesLeft -= bytesRead; + + if (RMIMasterSocketFactory.proxyLog.isLoggable(Log.VERBOSE)) { + RMIMasterSocketFactory.proxyLog.log(Log.VERBOSE, + "read " + bytesRead + " bytes, " + bytesLeft + " remaining"); + } + + return bytesRead; + } + + /** + * Mark the current position in the stream (for future calls to reset). + * Remember where we are within the proper content of the message, so + * that a reset method call can recreate our state properly. + * @param readlimit how many bytes can be read before mark becomes invalid + */ + public void mark(int readlimit) + { + in.mark(readlimit); + if (in.markSupported()) + bytesLeftAtMark = bytesLeft; + } + + /** + * Repositions the stream to the last marked position. Make sure to + * adjust our position within the proper content accordingly. + */ + public void reset() throws IOException + { + in.reset(); + bytesLeft = bytesLeftAtMark; + } + + /** + * Skips bytes of the stream. Make sure to adjust our + * position within the proper content accordingly. + * @param n number of bytes to be skipped + */ + public long skip(long n) throws IOException + { + if (n > bytesLeft) + n = bytesLeft; + long bytesSkipped = in.skip(n); + bytesLeft -= bytesSkipped; + return bytesSkipped; + } +} diff --git a/src/share/classes/sun/rmi/transport/proxy/HttpOutputStream.java b/src/share/classes/sun/rmi/transport/proxy/HttpOutputStream.java new file mode 100644 index 000000000..f7336b432 --- /dev/null +++ b/src/share/classes/sun/rmi/transport/proxy/HttpOutputStream.java @@ -0,0 +1,80 @@ +/* + * Copyright 1996 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ +package sun.rmi.transport.proxy; + +import java.io.*; + +/** + * The HttpOutputStream class assists the HttpSendSocket and HttpReceiveSocket + * classes by providing an output stream that buffers its entire input until + * closed, and then it sends the complete transmission prefixed by the end of + * an HTTP header that specifies the content length. + */ +class HttpOutputStream extends ByteArrayOutputStream { + + /** the output stream to send response to */ + protected OutputStream out; + + /** true if HTTP response has been sent */ + boolean responseSent = false; + + /** + * Begin buffering new HTTP response to be sent to a given stream. + * @param out the OutputStream to send response to + */ + public HttpOutputStream(OutputStream out) { + super(); + this.out = out; + } + + /** + * On close, send HTTP-packaged response. + */ + public synchronized void close() throws IOException { + if (!responseSent) { + /* + * If response would have zero content length, then make it + * have some arbitrary data so that certain clients will not + * fail because the "document contains no data". + */ + if (size() == 0) + write(emptyData); + + DataOutputStream dos = new DataOutputStream(out); + dos.writeBytes("Content-type: application/octet-stream\r\n"); + dos.writeBytes("Content-length: " + size() + "\r\n"); + dos.writeBytes("\r\n"); + writeTo(dos); + dos.flush(); + // Do not close the underlying stream here, because that would + // close the underlying socket and prevent reading a response. + reset(); // reset byte array + responseSent = true; + } + } + + /** data to send if the response would otherwise be empty */ + private static byte[] emptyData = { 0 }; +} diff --git a/src/share/classes/sun/rmi/transport/proxy/HttpReceiveSocket.java b/src/share/classes/sun/rmi/transport/proxy/HttpReceiveSocket.java new file mode 100644 index 000000000..5fc1ddb2b --- /dev/null +++ b/src/share/classes/sun/rmi/transport/proxy/HttpReceiveSocket.java @@ -0,0 +1,128 @@ +/* + * Copyright 1996-2000 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ +package sun.rmi.transport.proxy; + +import java.io.*; +import java.net.Socket; +import java.net.InetAddress; + +/** + * The HttpReceiveSocket class extends the WrappedSocket class + * by removing the HTTP protocol packaging from the input stream and + * formatting the output stream as an HTTP response. + * + * NOTES: + * + * The output stream must be explicitly closed for the output to be + * sent, since the HttpResponseOutputStream needs to buffer the entire + * transmission to be able to fill in the content-length field of + * the HTTP header. Closing this socket will do this. + * + * The constructor blocks until the HTTP protocol header + * is received. This could be fixed, but I don't think it should be a + * problem because this object would not be created unless the + * HttpAwareServerSocket has detected the beginning of the header + * anyway, so the rest should be there. + * + * This socket can only be used to process one POST and reply to it. + * Another message would be received on a newly accepted socket anyway. + */ +public class HttpReceiveSocket extends WrappedSocket implements RMISocketInfo { + + /** true if the HTTP header has pushed through the output stream yet */ + private boolean headerSent = false; + + /** + * Layer on top of a pre-existing Socket object, and use specified + * input and output streams. + * @param socket the pre-existing socket to use + * @param in the InputStream to use for this socket (can be null) + * @param out the OutputStream to use for this socket (can be null) + */ + public HttpReceiveSocket(Socket socket, InputStream in, OutputStream out) + throws IOException + { + super(socket, in, out); + + this.in = new HttpInputStream(in != null ? in : + socket.getInputStream()); + this.out = (out != null ? out : + socket.getOutputStream()); + } + + /** + * Indicate that this socket is not reusable. + */ + public boolean isReusable() + { + return false; + } + + /** + * Get the address to which this socket is connected. "null" is always + * returned (to indicate an unknown address) because the originating + * host's IP address cannot be reliably determined: both because the + * request probably went through a proxy server, and because if it was + * delivered by a local forwarder (CGI script or servlet), we do NOT + * want it to appear as if the call is coming from the local host (in + * case the remote object makes access control decisions based on the + * "client host" of a remote call; see bugid 4399040). + */ + public InetAddress getInetAddress() { + return null; + } + + /** + * Get an OutputStream for this socket. + */ + public OutputStream getOutputStream() throws IOException + { + if (!headerSent) { // could this be done in constructor?? + DataOutputStream dos = new DataOutputStream(out); + dos.writeBytes("HTTP/1.0 200 OK\r\n"); + dos.flush(); + headerSent = true; + out = new HttpOutputStream(out); + } + return out; + } + + /** + * Close the socket. + */ + public synchronized void close() throws IOException + { + getOutputStream().close(); // make sure response is sent + socket.close(); + } + + /** + * Return string representation of the socket. + */ + public String toString() + { + return "HttpReceive" + socket.toString(); + } +} diff --git a/src/share/classes/sun/rmi/transport/proxy/HttpSendInputStream.java b/src/share/classes/sun/rmi/transport/proxy/HttpSendInputStream.java new file mode 100644 index 000000000..48cba46cd --- /dev/null +++ b/src/share/classes/sun/rmi/transport/proxy/HttpSendInputStream.java @@ -0,0 +1,161 @@ +/* + * Copyright 1996 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ +package sun.rmi.transport.proxy; + +import java.io.*; + +/** + * The HttpSendInputStream class is used by the HttpSendSocket class as + * a layer on the top of the InputStream it returns so that it can be + * notified of attempts to read from it. This allows the HttpSendSocket + * to know when it should push across its output message. + */ +class HttpSendInputStream extends FilterInputStream { + + /** the HttpSendSocket object that is providing this stream */ + HttpSendSocket owner; + + /** + * Create new filter on a given input stream. + * @param in the InputStream to filter from + * @param owner the HttpSendSocket that is providing this stream + */ + public HttpSendInputStream(InputStream in, HttpSendSocket owner) + throws IOException + { + super(in); + + this.owner = owner; + } + + /** + * Mark this stream as inactive for its owner socket, so the next time + * a read is attempted, the owner will be notified and a new underlying + * input stream obtained. + */ + public void deactivate() + { + in = null; + } + + /** + * Read a byte of data from the stream. + */ + public int read() throws IOException + { + if (in == null) + in = owner.readNotify(); + return in.read(); + } + + /** + * Read into an array of bytes. + * @param b the buffer into which the data is to be read + * @param off the start offset of the data + * @param len the maximum number of bytes to read + */ + public int read(byte b[], int off, int len) throws IOException + { + if (len == 0) + return 0; + if (in == null) + in = owner.readNotify(); + return in.read(b, off, len); + } + + /** + * Skip bytes of input. + * @param n the number of bytes to be skipped + */ + public long skip(long n) throws IOException + { + if (n == 0) + return 0; + if (in == null) + in = owner.readNotify(); + return in.skip(n); + } + + /** + * Return the number of bytes that can be read without blocking. + */ + public int available() throws IOException + { + if (in == null) + in = owner.readNotify(); + return in.available(); + } + + /** + * Close the stream. + */ + public void close() throws IOException + { + owner.close(); + } + + /** + * Mark the current position in the stream. + * @param readlimit how many bytes can be read before mark becomes invalid + */ + public synchronized void mark(int readlimit) + { + if (in == null) { + try { + in = owner.readNotify(); + } + catch (IOException e) { + return; + } + } + in.mark(readlimit); + } + + /** + * Reposition the stream to the last marked position. + */ + public synchronized void reset() throws IOException + { + if (in == null) + in = owner.readNotify(); + in.reset(); + } + + /** + * Return true if this stream type supports mark/reset. + */ + public boolean markSupported() + { + if (in == null) { + try { + in = owner.readNotify(); + } + catch (IOException e) { + return false; + } + } + return in.markSupported(); + } +} diff --git a/src/share/classes/sun/rmi/transport/proxy/HttpSendOutputStream.java b/src/share/classes/sun/rmi/transport/proxy/HttpSendOutputStream.java new file mode 100644 index 000000000..b699cdd73 --- /dev/null +++ b/src/share/classes/sun/rmi/transport/proxy/HttpSendOutputStream.java @@ -0,0 +1,105 @@ +/* + * Copyright 1996 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ +package sun.rmi.transport.proxy; + +import java.io.*; + +/** + * The HttpSendOutputStream class is used by the HttpSendSocket class as + * a layer on the top of the OutputStream it returns so that it can be + * notified of attempts to write to it. This allows the HttpSendSocket + * to know when it should construct a new message. + */ +class HttpSendOutputStream extends FilterOutputStream { + + /** the HttpSendSocket object that is providing this stream */ + HttpSendSocket owner; + + /** + * Create new filter on a given output stream. + * @param out the OutputStream to filter from + * @param owner the HttpSendSocket that is providing this stream + */ + public HttpSendOutputStream(OutputStream out, HttpSendSocket owner) + throws IOException + { + super(out); + + this.owner = owner; + } + + /** + * Mark this stream as inactive for its owner socket, so the next time + * a write is attempted, the owner will be notified and a new underlying + * output stream obtained. + */ + public void deactivate() + { + out = null; + } + + /** + * Write a byte of data to the stream. + */ + public void write(int b) throws IOException + { + if (out == null) + out = owner.writeNotify(); + out.write(b); + } + + /** + * Write a subarray of bytes. + * @param b the buffer from which the data is to be written + * @param off the start offset of the data + * @param len the number of bytes to be written + */ + public void write(byte b[], int off, int len) throws IOException + { + if (len == 0) + return; + if (out == null) + out = owner.writeNotify(); + out.write(b, off, len); + } + + /** + * Flush the stream. + */ + public void flush() throws IOException + { + if (out != null) + out.flush(); + } + + /** + * Close the stream. + */ + public void close() throws IOException + { + flush(); + owner.close(); + } +} diff --git a/src/share/classes/sun/rmi/transport/proxy/HttpSendSocket.java b/src/share/classes/sun/rmi/transport/proxy/HttpSendSocket.java new file mode 100644 index 000000000..7498c9bfd --- /dev/null +++ b/src/share/classes/sun/rmi/transport/proxy/HttpSendSocket.java @@ -0,0 +1,343 @@ +/* + * Copyright 1996-2003 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ +package sun.rmi.transport.proxy; + +import java.io.*; +import java.net.*; + +import sun.rmi.runtime.Log; + +/** + * The HttpSendSocket class extends the java.net.Socket class + * by enclosing the data output stream in, then extracting the input + * stream from, an HTTP protocol transmission. + * + * NOTES: + * + * Since the length of the output request must be known before the + * HTTP header can be completed, all of the output is buffered by + * an HttpOutputStream object until either an attempt is made to + * read from this socket, or the socket is explicitly closed. + * + * On the first read attempt to read from this socket, the buffered + * output is sent to the destination as the body of an HTTP POST + * request. All reads will then acquire data from the body of + * the response. A subsequent attempt to write to this socket will + * throw an IOException. + */ +class HttpSendSocket extends Socket implements RMISocketInfo { + + /** the host to connect to */ + protected String host; + + /** the port to connect to */ + protected int port; + + /** the URL to forward through */ + protected URL url; + + /** the object managing this connection through the URL */ + protected URLConnection conn = null; + + /** internal input stream for this socket */ + protected InputStream in = null; + + /** internal output stream for this socket */ + protected OutputStream out = null; + + /** the notifying input stream returned to users */ + protected HttpSendInputStream inNotifier; + + /** the notifying output stream returned to users */ + protected HttpSendOutputStream outNotifier; + + /** + * Line separator string. This is the value of the line.separator + * property at the moment that the socket was created. + */ + private String lineSeparator = + (String) java.security.AccessController.doPrivileged( + new sun.security.action.GetPropertyAction("line.separator")); + + /** + * Create a stream socket and connect it to the specified port on + * the specified host. + * @param host the host + * @param port the port + */ + public HttpSendSocket(String host, int port, URL url) throws IOException + { + super((SocketImpl)null); // no underlying SocketImpl for this object + + if (RMIMasterSocketFactory.proxyLog.isLoggable(Log.VERBOSE)) { + RMIMasterSocketFactory.proxyLog.log(Log.VERBOSE, + "host = " + host + ", port = " + port + ", url = " + url); + } + + this.host = host; + this.port = port; + this.url = url; + + inNotifier = new HttpSendInputStream(null, this); + outNotifier = new HttpSendOutputStream(writeNotify(), this); + } + + /** + * Create a stream socket and connect it to the specified port on + * the specified host. + * @param host the host + * @param port the port + */ + public HttpSendSocket(String host, int port) throws IOException + { + this(host, port, new URL("http", host, port, "/")); + } + + /** + * Create a stream socket and connect it to the specified address on + * the specified port. + * @param address the address + * @param port the port + */ + public HttpSendSocket(InetAddress address, int port) throws IOException + { + this(address.getHostName(), port); + } + + /** + * Indicate that this socket is not reusable. + */ + public boolean isReusable() + { + return false; + } + + /** + * Create a new socket connection to host (or proxy), and prepare to + * send HTTP transmission. + */ + public synchronized OutputStream writeNotify() throws IOException + { + if (conn != null) { + throw new IOException("attempt to write on HttpSendSocket after " + + "request has been sent"); + } + + conn = url.openConnection(); + conn.setDoOutput(true); + conn.setUseCaches(false); + conn.setRequestProperty("Content-type", "application/octet-stream"); + + inNotifier.deactivate(); + in = null; + + return out = conn.getOutputStream(); + } + + /** + * Send HTTP output transmission and prepare to receive response. + */ + public synchronized InputStream readNotify() throws IOException + { + RMIMasterSocketFactory.proxyLog.log(Log.VERBOSE, + "sending request and activating input stream"); + + outNotifier.deactivate(); + out.close(); + out = null; + + try { + in = conn.getInputStream(); + } catch (IOException e) { + RMIMasterSocketFactory.proxyLog.log(Log.BRIEF, + "failed to get input stream, exception: ", e); + + throw new IOException("HTTP request failed"); + } + + /* + * If an HTTP error response is returned, sometimes an IOException + * is thrown, which is handled above, and other times it isn't, and + * the error response body will be available for reading. + * As a safety net to catch any such unexpected HTTP behavior, we + * verify that the content type of the response is what the + * HttpOutputStream generates: "application/octet-stream". + * (Servers' error responses will generally be "text/html".) + * Any error response body is printed to the log. + */ + String contentType = conn.getContentType(); + if (contentType == null || + !conn.getContentType().equals("application/octet-stream")) + { + if (RMIMasterSocketFactory.proxyLog.isLoggable(Log.BRIEF)) { + String message; + if (contentType == null) { + message = "missing content type in response" + + lineSeparator; + } else { + message = "invalid content type in response: " + + contentType + lineSeparator; + } + + message += "HttpSendSocket.readNotify: response body: "; + try { + DataInputStream din = new DataInputStream(in); + String line; + while ((line = din.readLine()) != null) + message += line + lineSeparator; + } catch (IOException e) { + } + RMIMasterSocketFactory.proxyLog.log(Log.BRIEF, message); + } + + throw new IOException("HTTP request failed"); + } + + return in; + } + + /** + * Get the address to which the socket is connected. + */ + public InetAddress getInetAddress() + { + try { + return InetAddress.getByName(host); + } catch (UnknownHostException e) { + return null; // null if couldn't resolve destination host + } + } + + /** + * Get the local address to which the socket is bound. + */ + public InetAddress getLocalAddress() + { + try { + return InetAddress.getLocalHost(); + } catch (UnknownHostException e) { + return null; // null if couldn't determine local host + } + } + + /** + * Get the remote port to which the socket is connected. + */ + public int getPort() + { + return port; + } + + /** + * Get the local port to which the socket is connected. + */ + public int getLocalPort() + { + return -1; // request not applicable to this socket type + } + + /** + * Get an InputStream for this socket. + */ + public InputStream getInputStream() throws IOException + { + return inNotifier; + } + + /** + * Get an OutputStream for this socket. + */ + public OutputStream getOutputStream() throws IOException + { + return outNotifier; + } + + /** + * Enable/disable TCP_NODELAY. + * This operation has no effect for an HttpSendSocket. + */ + public void setTcpNoDelay(boolean on) throws SocketException + { + } + + /** + * Retrieve whether TCP_NODELAY is enabled. + */ + public boolean getTcpNoDelay() throws SocketException + { + return false; // imply option is disabled + } + + /** + * Enable/disable SO_LINGER with the specified linger time. + * This operation has no effect for an HttpSendSocket. + */ + public void setSoLinger(boolean on, int val) throws SocketException + { + } + + /** + * Retrive setting for SO_LINGER. + */ + public int getSoLinger() throws SocketException + { + return -1; // imply option is disabled + } + + /** + * Enable/disable SO_TIMEOUT with the specified timeout + * This operation has no effect for an HttpSendSocket. + */ + public synchronized void setSoTimeout(int timeout) throws SocketException + { + } + + /** + * Retrive setting for SO_TIMEOUT. + */ + public synchronized int getSoTimeout() throws SocketException + { + return 0; // imply option is disabled + } + + /** + * Close the socket. + */ + public synchronized void close() throws IOException + { + if (out != null) // push out transmission if not done + out.close(); + } + + /** + * Return string representation of this pseudo-socket. + */ + public String toString() + { + return "HttpSendSocket[host=" + host + + ",port=" + port + + ",url=" + url + "]"; + } +} diff --git a/src/share/classes/sun/rmi/transport/proxy/RMIDirectSocketFactory.java b/src/share/classes/sun/rmi/transport/proxy/RMIDirectSocketFactory.java new file mode 100644 index 000000000..e687f7618 --- /dev/null +++ b/src/share/classes/sun/rmi/transport/proxy/RMIDirectSocketFactory.java @@ -0,0 +1,47 @@ +/* + * Copyright 1996 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ +package sun.rmi.transport.proxy; + +import java.io.IOException; +import java.net.Socket; +import java.net.ServerSocket; +import java.rmi.server.RMISocketFactory; + +/** + * RMIDirectSocketFactory creates a direct socket connection to the + * specified port on the specified host. + */ +public class RMIDirectSocketFactory extends RMISocketFactory { + + public Socket createSocket(String host, int port) throws IOException + { + return new Socket(host, port); + } + + public ServerSocket createServerSocket(int port) throws IOException + { + return new ServerSocket(port); + } +} diff --git a/src/share/classes/sun/rmi/transport/proxy/RMIHttpToCGISocketFactory.java b/src/share/classes/sun/rmi/transport/proxy/RMIHttpToCGISocketFactory.java new file mode 100644 index 000000000..b9ed241f2 --- /dev/null +++ b/src/share/classes/sun/rmi/transport/proxy/RMIHttpToCGISocketFactory.java @@ -0,0 +1,55 @@ +/* + * Copyright 1998 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ +package sun.rmi.transport.proxy; + +import java.io.IOException; +import java.net.Socket; +import java.net.ServerSocket; +import java.net.URL; +import java.rmi.server.RMISocketFactory; + +/** + * RMIHttpToCGISocketFactory creates a socket connection to the + * specified host that is comminicated within an HTTP request, + * forwarded through the default firewall proxy, to the target host's + * normal HTTP server, to a CGI program which forwards the request to + * the actual specified port on the socket. + */ +public class RMIHttpToCGISocketFactory extends RMISocketFactory { + + public Socket createSocket(String host, int port) + throws IOException + { + return new HttpSendSocket(host, port, + new URL("http", host, + "/cgi-bin/java-rmi.cgi" + + "?forward=" + port)); + } + + public ServerSocket createServerSocket(int port) throws IOException + { + return new HttpAwareServerSocket(port); + } +} diff --git a/src/share/classes/sun/rmi/transport/proxy/RMIHttpToPortSocketFactory.java b/src/share/classes/sun/rmi/transport/proxy/RMIHttpToPortSocketFactory.java new file mode 100644 index 000000000..7a6e7a9d5 --- /dev/null +++ b/src/share/classes/sun/rmi/transport/proxy/RMIHttpToPortSocketFactory.java @@ -0,0 +1,53 @@ +/* + * Copyright 1998 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ +package sun.rmi.transport.proxy; + +import java.io.IOException; +import java.net.Socket; +import java.net.ServerSocket; +import java.net.URL; +import java.rmi.server.RMISocketFactory; + +/** + * RMIHttpToPortSocketFactory creates a socket connection to the + * specified host that is communicated within an HTTP request, + * forwarded through the default firewall proxy, directly to the + * specified port. + */ +public class RMIHttpToPortSocketFactory extends RMISocketFactory { + + public Socket createSocket(String host, int port) + throws IOException + { + return new HttpSendSocket(host, port, + new URL("http", host, port, "/")); + } + + public ServerSocket createServerSocket(int port) + throws IOException + { + return new HttpAwareServerSocket(port); + } +} diff --git a/src/share/classes/sun/rmi/transport/proxy/RMIMasterSocketFactory.java b/src/share/classes/sun/rmi/transport/proxy/RMIMasterSocketFactory.java new file mode 100644 index 000000000..16735959e --- /dev/null +++ b/src/share/classes/sun/rmi/transport/proxy/RMIMasterSocketFactory.java @@ -0,0 +1,478 @@ +/* + * Copyright 1996-2002 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ +package sun.rmi.transport.proxy; + +import java.io.*; +import java.net.*; +import java.security.*; +import java.util.*; +import java.rmi.server.LogStream; +import java.rmi.server.RMISocketFactory; +import sun.rmi.runtime.Log; +import sun.rmi.runtime.NewThreadAction; +import sun.security.action.GetBooleanAction; +import sun.security.action.GetLongAction; + +/** + * RMIMasterSocketFactory attempts to create a socket connection to the + * specified host using successively less efficient mechanisms + * until one succeeds. If the host is successfully connected to, + * the factory for the successful mechanism is stored in an internal + * hash table keyed by the host name, so that future attempts to + * connect to the same host will automatically use the same + * mechanism. + */ +public class RMIMasterSocketFactory extends RMISocketFactory { + + /** "proxy" package log level */ + static int logLevel = LogStream.parseLevel(getLogLevel()); + + private static String getLogLevel() { + return (String) java.security.AccessController.doPrivileged( + new sun.security.action.GetPropertyAction("sun.rmi.transport.proxy.logLevel")); + } + + /* proxy package log */ + static final Log proxyLog = + Log.getLog("sun.rmi.transport.tcp.proxy", + "transport", RMIMasterSocketFactory.logLevel); + + /** timeout for attemping direct socket connections */ + private static long connectTimeout = getConnectTimeout(); + + private static long getConnectTimeout() { + return ((Long) java.security.AccessController.doPrivileged( + new GetLongAction("sun.rmi.transport.proxy.connectTimeout", + 15000))).longValue(); // default: 15 seconds + } + + /** whether to fallback to HTTP on general connect failures */ + private static final boolean eagerHttpFallback = ((Boolean) + java.security.AccessController.doPrivileged(new GetBooleanAction( + "sun.rmi.transport.proxy.eagerHttpFallback"))).booleanValue(); + + /** table of hosts successfully connected to and the factory used */ + private Hashtable successTable = new Hashtable(); + + /** maximum number of hosts to remember successful connection to */ + private static final int MaxRememberedHosts = 64; + + /** list of the hosts in successTable in initial connection order */ + private Vector hostList = new Vector(MaxRememberedHosts); + + /** default factory to initally use for direct socket connection */ + protected RMISocketFactory initialFactory = new RMIDirectSocketFactory(); + + /** ordered list of factories to try as alternate connection + * mechanisms if a direct socket connections fails */ + protected Vector altFactoryList; + + /** + * Create a RMIMasterSocketFactory object. Establish order of + * connection mechanisms to attempt on createSocket, if a direct + * socket connection fails. + */ + public RMIMasterSocketFactory() { + altFactoryList = new Vector(2); + boolean setFactories = false; + + try { + String proxyHost; + proxyHost = (String) java.security.AccessController.doPrivileged( + new sun.security.action.GetPropertyAction("http.proxyHost")); + + if (proxyHost == null) + proxyHost=(String)java.security.AccessController.doPrivileged( + new sun.security.action.GetPropertyAction("proxyHost")); + + Boolean tmp = (Boolean)java.security.AccessController.doPrivileged( + new sun.security.action.GetBooleanAction("java.rmi.server.disableHttp")); + + if (!tmp.booleanValue() && + (proxyHost != null && proxyHost.length() > 0)) { + setFactories = true; + } + } catch (Exception e) { + // unable to obtain the properties, so assume default behavior. + setFactories = true; + } + + if (setFactories) { + altFactoryList.addElement(new RMIHttpToPortSocketFactory()); + altFactoryList.addElement(new RMIHttpToCGISocketFactory()); + } + } + + /** + * Create a new client socket. If we remember connecting to this host + * successfully before, then use the same factory again. Otherwise, + * try using a direct socket connection and then the alternate factories + * in the order specified in altFactoryList. + */ + public Socket createSocket(String host, int port) + throws IOException + { + if (proxyLog.isLoggable(Log.BRIEF)) { + proxyLog.log(Log.BRIEF, "host: " + host + ", port: " + port); + } + + /* + * If we don't have any alternate factories to consult, short circuit + * the fallback procedure and delegate to the initial factory. + */ + if (altFactoryList.size() == 0) { + return initialFactory.createSocket(host, port); + } + + RMISocketFactory factory; + + /* + * If we remember successfully connecting to this host before, + * use the same factory. + */ + factory = (RMISocketFactory) successTable.get(host); + if (factory != null) { + if (proxyLog.isLoggable(Log.BRIEF)) { + proxyLog.log(Log.BRIEF, + "previously successful factory found: " + factory); + } + return factory.createSocket(host, port); + } + + /* + * Next, try a direct socket connection. Open socket in another + * thread and only wait for specified timeout, in case the socket + * would otherwise spend minutes trying an unreachable host. + */ + Socket initialSocket = null; + Socket fallbackSocket = null; + final AsyncConnector connector = + new AsyncConnector(initialFactory, host, port, + AccessController.getContext()); + // connection must be attempted with + // this thread's access control context + IOException initialFailure = null; + + try { + synchronized (connector) { + + Thread t = (Thread) + java.security.AccessController.doPrivileged( + new NewThreadAction(connector, "AsyncConnector", + true)); + t.start(); + + try { + long now = System.currentTimeMillis(); + long deadline = now + connectTimeout; + do { + connector.wait(deadline - now); + initialSocket = checkConnector(connector); + if (initialSocket != null) + break; + now = System.currentTimeMillis(); + } while (now < deadline); + } catch (InterruptedException e) { + throw new InterruptedIOException( + "interrupted while waiting for connector"); + } + } + + // assume no route to host (for now) if no connection yet + if (initialSocket == null) + throw new NoRouteToHostException( + "connect timed out: " + host); + + proxyLog.log(Log.BRIEF, "direct socket connection successful"); + + return initialSocket; + + } catch (UnknownHostException e) { + initialFailure = e; + } catch (NoRouteToHostException e) { + initialFailure = e; + } catch (SocketException e) { + if (eagerHttpFallback) { + initialFailure = e; + } else { + throw e; + } + } finally { + if (initialFailure != null) { + + if (proxyLog.isLoggable(Log.BRIEF)) { + proxyLog.log(Log.BRIEF, + "direct socket connection failed: ", initialFailure); + } + + // Finally, try any alternate connection mechanisms. + for (int i = 0; i < altFactoryList.size(); ++ i) { + factory = (RMISocketFactory) altFactoryList.elementAt(i); + try { + if (proxyLog.isLoggable(Log.BRIEF)) { + proxyLog.log(Log.BRIEF, + "trying with factory: " + factory); + } + + // For HTTP connections, the output (POST request) must + // be sent before we verify a successful connection. + // So, sacrifice a socket for the sake of testing... + // The following sequence should verify a successful + // HTTP connection if no IOException is thrown. + Socket testSocket = factory.createSocket(host, port); + InputStream in = testSocket.getInputStream(); + int b = in.read(); // probably -1 for EOF... + testSocket.close(); + } catch (IOException ex) { + if (proxyLog.isLoggable(Log.BRIEF)) { + proxyLog.log(Log.BRIEF, "factory failed: ", ex); + } + + continue; + } + proxyLog.log(Log.BRIEF, "factory succeeded"); + + // factory succeeded, open new socket for caller's use + try { + fallbackSocket = factory.createSocket(host, port); + } catch (IOException ex) { // if it fails 2nd time, + } // just give up + break; + } + } + } + + synchronized (successTable) { + try { + // check once again to see if direct connection succeeded + synchronized (connector) { + initialSocket = checkConnector(connector); + } + if (initialSocket != null) { + // if we had made another one as well, clean it up... + if (fallbackSocket != null) + fallbackSocket.close(); + return initialSocket; + } + // if connector ever does get socket, it won't be used + connector.notUsed(); + } catch (UnknownHostException e) { + initialFailure = e; + } catch (NoRouteToHostException e) { + initialFailure = e; + } catch (SocketException e) { + if (eagerHttpFallback) { + initialFailure = e; + } else { + throw e; + } + } + // if we had found an alternate mechanism, go and use it + if (fallbackSocket != null) { + // remember this successful host/factory pair + rememberFactory(host, factory); + return fallbackSocket; + } + throw initialFailure; + } + } + + /** + * Remember a successful factory for connecting to host. + * Currently, excess hosts are removed from the remembered list + * using a Least Recently Created strategy. + */ + void rememberFactory(String host, RMISocketFactory factory) { + synchronized (successTable) { + while (hostList.size() >= MaxRememberedHosts) { + successTable.remove(hostList.elementAt(0)); + hostList.removeElementAt(0); + } + hostList.addElement(host); + successTable.put(host, factory); + } + } + + /** + * Check if an AsyncConnector succeeded. If not, return socket + * given to fall back to. + */ + Socket checkConnector(AsyncConnector connector) + throws IOException + { + Exception e = connector.getException(); + if (e != null) { + e.fillInStackTrace(); + /* + * The AsyncConnector implementation guaranteed that the exception + * will be either an IOException or a RuntimeException, and we can + * only throw one of those, so convince that compiler that it must + * be one of those. + */ + if (e instanceof IOException) { + throw (IOException) e; + } else if (e instanceof RuntimeException) { + throw (RuntimeException) e; + } else { + throw new Error("internal error: " + + "unexpected checked exception: " + e.toString()); + } + } + return connector.getSocket(); + } + + /** + * Create a new server socket. + */ + public ServerSocket createServerSocket(int port) throws IOException { + //return new HttpAwareServerSocket(port); + return initialFactory.createServerSocket(port); + } + + + /** + * AsyncConnector is used by RMIMasterSocketFactory to attempt socket + * connections on a separate thread. This allows RMIMasterSocketFactory + * to control how long it will wait for the connection to succeed. + */ + private class AsyncConnector implements Runnable { + + /** what factory to use to attempt connection */ + private RMISocketFactory factory; + + /** the host to connect to */ + private String host; + + /** the port to connect to */ + private int port; + + /** access control context to attempt connection within */ + private AccessControlContext acc; + + /** exception that occurred during connection, if any */ + private Exception exception = null; + + /** the connected socket, if successful */ + private Socket socket = null; + + /** socket should be closed after created, if ever */ + private boolean cleanUp = false; + + /** + * Create a new asynchronous connector object. + */ + AsyncConnector(RMISocketFactory factory, String host, int port, + AccessControlContext acc) + { + this.factory = factory; + this.host = host; + this.port = port; + this.acc = acc; + SecurityManager security = System.getSecurityManager(); + if (security != null) { + security.checkConnect(host, port); + } + } + + /** + * Attempt socket connection in separate thread. If successful, + * notify master waiting, + */ + public void run() { + try { + /* + * Using the privileges of the thread that wants to make the + * connection is tempting, but it will fail with applets with + * the current applet security manager because the applet + * network connection policy is not captured in the permission + * framework of the access control context we have. + * + * java.security.AccessController.beginPrivileged(acc); + */ + try { + Socket temp = factory.createSocket(host, port); + synchronized (this) { + socket = temp; + notify(); + } + rememberFactory(host, factory); + synchronized (this) { + if (cleanUp) + try { + socket.close(); + } catch (IOException e) { + } + } + } catch (Exception e) { + /* + * Note that the only exceptions which could actually have + * occurred here are IOException or RuntimeException. + */ + synchronized (this) { + exception = e; + notify(); + } + } + } finally { + /* + * See above comments for matching beginPrivileged() call that + * is also commented out. + * + * java.security.AccessController.endPrivileged(); + */ + } + } + + /** + * Get exception that occurred during connection attempt, if any. + * In the current implementation, this is guaranteed to be either + * an IOException or a RuntimeException. + */ + private synchronized Exception getException() { + return exception; + } + + /** + * Get successful socket, if any. + */ + private synchronized Socket getSocket() { + return socket; + } + + /** + * Note that this connector's socket, if ever successfully created, + * will not be used, so it should be cleaned up quickly + */ + synchronized void notUsed() { + if (socket != null) { + try { + socket.close(); + } catch (IOException e) { + } + } + cleanUp = true; + } + } +} diff --git a/src/share/classes/sun/rmi/transport/proxy/RMISocketInfo.java b/src/share/classes/sun/rmi/transport/proxy/RMISocketInfo.java new file mode 100644 index 000000000..e09cfcd28 --- /dev/null +++ b/src/share/classes/sun/rmi/transport/proxy/RMISocketInfo.java @@ -0,0 +1,39 @@ +/* + * Copyright 1996 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ +package sun.rmi.transport.proxy; + +/** + * RMISocketInfo is an interface that extensions of the java.net.Socket + * class may use to provide more information on its capabilities. + */ +public interface RMISocketInfo { + + /** + * Return true if this socket can be used for more than one + * RMI call. If a socket does not implement this interface, then + * it is assumed to be reusable. + */ + public boolean isReusable(); +} diff --git a/src/share/classes/sun/rmi/transport/proxy/WrappedSocket.java b/src/share/classes/sun/rmi/transport/proxy/WrappedSocket.java new file mode 100644 index 000000000..7671a5122 --- /dev/null +++ b/src/share/classes/sun/rmi/transport/proxy/WrappedSocket.java @@ -0,0 +1,183 @@ +/* + * Copyright 1996-2003 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ +package sun.rmi.transport.proxy; + +import java.io.*; +import java.net.InetAddress; +import java.net.Socket; +import java.net.SocketException; + +/** + * The WrappedSocket class provides a general wrapper for providing an + * extended implementation of java.net.Socket that can be attached to + * a pre-existing Socket object. WrappedSocket itself provides a + * constructor for specifying alternate input or output streams to be + * returned than those of the underlying Socket. + */ +class WrappedSocket extends Socket { + + /** the underlying concrete socket */ + protected Socket socket; + + /** the input stream to return for socket */ + protected InputStream in = null; + + /** the output stream to return for socket */ + protected OutputStream out = null; + + /** + * Layer on top of a pre-existing Socket object, and use specified + * input and output streams. This allows the creator of the + * underlying socket to peek at the beginning of the input with a + * BufferedInputStream and determine which kind of socket + * to create, without consuming the input. + * @param socket the pre-existing socket to use + * @param in the InputStream to return to users (can be null) + * @param out the OutputStream to return to users (can be null) + */ + public WrappedSocket(Socket socket, InputStream in, OutputStream out) + throws IOException + { + super((java.net.SocketImpl)null); // no underlying SocketImpl for this object + this.socket = socket; + this.in = in; + this.out = out; + } + + /** + * Get the address to which the socket is connected. + */ + public InetAddress getInetAddress() + { + return socket.getInetAddress(); + } + + /** + * Get the local address to which the socket is bound. + */ + public InetAddress getLocalAddress() { + return socket.getLocalAddress(); + } + + /** + * Get the remote port to which the socket is connected. + */ + public int getPort() + { + return socket.getPort(); + } + + /** + * Get the local port to which the socket is connected. + */ + public int getLocalPort() + { + return socket.getLocalPort(); + } + + /** + * Get an InputStream for this socket. + */ + public InputStream getInputStream() throws IOException + { + if (in == null) + in = socket.getInputStream(); + return in; + } + + /** + * Get an OutputStream for this socket. + */ + public OutputStream getOutputStream() throws IOException + { + if (out == null) + out = socket.getOutputStream(); + return out; + } + + /** + * Enable/disable TCP_NODELAY. + */ + public void setTcpNoDelay(boolean on) throws SocketException + { + socket.setTcpNoDelay(on); + } + + /** + * Retrieve whether TCP_NODELAY is enabled. + */ + public boolean getTcpNoDelay() throws SocketException + { + return socket.getTcpNoDelay(); + } + + /** + * Enable/disable SO_LINGER with the specified linger time. + */ + public void setSoLinger(boolean on, int val) throws SocketException + { + socket.setSoLinger(on, val); + } + + /** + * Retrive setting for SO_LINGER. + */ + public int getSoLinger() throws SocketException + { + return socket.getSoLinger(); + } + + /** + * Enable/disable SO_TIMEOUT with the specified timeout + */ + public synchronized void setSoTimeout(int timeout) throws SocketException + { + socket.setSoTimeout(timeout); + } + + /** + * Retrive setting for SO_TIMEOUT. + */ + public synchronized int getSoTimeout() throws SocketException + { + return socket.getSoTimeout(); + } + + /** + * Close the socket. + */ + public synchronized void close() throws IOException + { + socket.close(); + } + + /** + * Return string representation of the socket. + */ + public String toString() + { + return "Wrapped" + socket.toString(); + } +} diff --git a/src/share/classes/sun/rmi/transport/tcp/ConnectionMultiplexer.java b/src/share/classes/sun/rmi/transport/tcp/ConnectionMultiplexer.java new file mode 100644 index 000000000..f756cd34f --- /dev/null +++ b/src/share/classes/sun/rmi/transport/tcp/ConnectionMultiplexer.java @@ -0,0 +1,459 @@ +/* + * Copyright 1996-2003 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ +package sun.rmi.transport.tcp; + +import java.io.*; +import java.util.*; +import java.rmi.server.LogStream; + +import sun.rmi.runtime.Log; + +/** + * ConnectionMultiplexer manages the transparent multiplexing of + * multiple virtual connections from one endpoint to another through + * one given real connection to that endpoint. The input and output + * streams for the the underlying real connection must be supplied. + * A callback object is also supplied to be informed of new virtual + * connections opened by the remote endpoint. After creation, the + * run() method must be called in a thread created for demultiplexing + * the connections. The openConnection() method is called to + * initiate a virtual connection from this endpoint. + * + * @author Peter Jones + */ +final class ConnectionMultiplexer { + + /** "multiplex" log level */ + static int logLevel = LogStream.parseLevel(getLogLevel()); + + private static String getLogLevel() { + return (String) java.security.AccessController.doPrivileged( + new sun.security.action.GetPropertyAction("sun.rmi.transport.tcp.multiplex.logLevel")); + } + + /* multiplex system log */ + static final Log multiplexLog = + Log.getLog("sun.rmi.transport.tcp.multiplex", + "multiplex", ConnectionMultiplexer.logLevel); + + /** multiplexing protocol operation codes */ + private final static int OPEN = 0xE1; + private final static int CLOSE = 0xE2; + private final static int CLOSEACK = 0xE3; + private final static int REQUEST = 0xE4; + private final static int TRANSMIT = 0xE5; + + /** object to notify for new connections from remote endpoint */ + private TCPChannel channel; + + /** input stream for underlying single connection */ + private InputStream in; + + /** output stream for underlying single connection */ + private OutputStream out; + + /** true if underlying connection originated from this endpoint + (used for generating unique connection IDs) */ + private boolean orig; + + /** layered stream for reading formatted data from underlying connection */ + private DataInputStream dataIn; + + /** layered stream for writing formatted data to underlying connection */ + private DataOutputStream dataOut; + + /** table holding currently open connection IDs and related info */ + private Hashtable connectionTable = new Hashtable(7); + + /** number of currently open connections */ + private int numConnections = 0; + + /** maximum allowed open connections */ + private final static int maxConnections = 256; + + /** ID of last connection opened */ + private int lastID = 0x1001; + + /** true if this mechanism is still alive */ + private boolean alive = true; + + /** + * Create a new ConnectionMultiplexer using the given underlying + * input/output stream pair. The run method must be called + * (possibly on a new thread) to handle the demultiplexing. + * @param channel object to notify when new connection is received + * @param in input stream of underlying connection + * @param out output stream of underlying connection + * @param orig true if this endpoint intiated the underlying + * connection (needs to be set differently at both ends) + */ + public ConnectionMultiplexer( + TCPChannel channel, + InputStream in, + OutputStream out, + boolean orig) + { + this.channel = channel; + this.in = in; + this.out = out; + this.orig = orig; + + dataIn = new DataInputStream(in); + dataOut = new DataOutputStream(out); + } + + /** + * Process multiplexing protocol received from underlying connection. + */ + public void run() throws IOException + { + try { + int op, id, length; + Integer idObj; + MultiplexConnectionInfo info; + + while (true) { + + // read next op code from remote endpoint + op = dataIn.readUnsignedByte(); + switch (op) { + + // remote endpoint initiating new connection + case OPEN: + id = dataIn.readUnsignedShort(); + + if (multiplexLog.isLoggable(Log.VERBOSE)) { + multiplexLog.log(Log.VERBOSE, "operation OPEN " + id); + } + + idObj = new Integer(id); + info = + (MultiplexConnectionInfo) connectionTable.get(idObj); + if (info != null) + throw new IOException( + "OPEN: Connection ID already exists"); + info = new MultiplexConnectionInfo(id); + info.in = new MultiplexInputStream(this, info, 2048); + info.out = new MultiplexOutputStream(this, info, 2048); + synchronized (connectionTable) { + connectionTable.put(idObj, info); + ++ numConnections; + } + sun.rmi.transport.Connection conn; + conn = new TCPConnection(channel, info.in, info.out); + channel.acceptMultiplexConnection(conn); + break; + + // remote endpoint closing connection + case CLOSE: + id = dataIn.readUnsignedShort(); + + if (multiplexLog.isLoggable(Log.VERBOSE)) { + multiplexLog.log(Log.VERBOSE, "operation CLOSE " + id); + } + + idObj = new Integer(id); + info = + (MultiplexConnectionInfo) connectionTable.get(idObj); + if (info == null) + throw new IOException( + "CLOSE: Invalid connection ID"); + info.in.disconnect(); + info.out.disconnect(); + if (!info.closed) + sendCloseAck(info); + synchronized (connectionTable) { + connectionTable.remove(idObj); + -- numConnections; + } + break; + + // remote endpoint acknowledging close of connection + case CLOSEACK: + id = dataIn.readUnsignedShort(); + + if (multiplexLog.isLoggable(Log.VERBOSE)) { + multiplexLog.log(Log.VERBOSE, + "operation CLOSEACK " + id); + } + + idObj = new Integer(id); + info = + (MultiplexConnectionInfo) connectionTable.get(idObj); + if (info == null) + throw new IOException( + "CLOSEACK: Invalid connection ID"); + if (!info.closed) + throw new IOException( + "CLOSEACK: Connection not closed"); + info.in.disconnect(); + info.out.disconnect(); + synchronized (connectionTable) { + connectionTable.remove(idObj); + -- numConnections; + } + break; + + // remote endpoint declaring additional bytes receivable + case REQUEST: + id = dataIn.readUnsignedShort(); + idObj = new Integer(id); + info = + (MultiplexConnectionInfo) connectionTable.get(idObj); + if (info == null) + throw new IOException( + "REQUEST: Invalid connection ID"); + length = dataIn.readInt(); + + if (multiplexLog.isLoggable(Log.VERBOSE)) { + multiplexLog.log(Log.VERBOSE, + "operation REQUEST " + id + ": " + length); + } + + info.out.request(length); + break; + + // remote endpoint transmitting data packet + case TRANSMIT: + id = dataIn.readUnsignedShort(); + idObj = new Integer(id); + info = + (MultiplexConnectionInfo) connectionTable.get(idObj); + if (info == null) + throw new IOException("SEND: Invalid connection ID"); + length = dataIn.readInt(); + + if (multiplexLog.isLoggable(Log.VERBOSE)) { + multiplexLog.log(Log.VERBOSE, + "operation TRANSMIT " + id + ": " + length); + } + + info.in.receive(length, dataIn); + break; + + default: + throw new IOException("Invalid operation: " + + Integer.toHexString(op)); + } + } + } finally { + shutDown(); + } + } + + /** + * Initiate a new multiplexed connection through the underlying + * connection. + */ + public synchronized TCPConnection openConnection() throws IOException + { + // generate ID that should not be already used + // If all possible 32768 IDs are used, + // this method will block searching for a new ID forever. + int id; + Integer idObj; + do { + lastID = (++ lastID) & 0x7FFF; + id = lastID; + + // The orig flag (copied to the high bit of the ID) is used + // to have two distinct ranges to choose IDs from for the + // two endpoints. + if (orig) + id |= 0x8000; + idObj = new Integer(id); + } while (connectionTable.get(idObj) != null); + + // create multiplexing streams and bookkeeping information + MultiplexConnectionInfo info = new MultiplexConnectionInfo(id); + info.in = new MultiplexInputStream(this, info, 2048); + info.out = new MultiplexOutputStream(this, info, 2048); + + // add to connection table if multiplexer has not died + synchronized (connectionTable) { + if (!alive) + throw new IOException("Multiplexer connection dead"); + if (numConnections >= maxConnections) + throw new IOException("Cannot exceed " + maxConnections + + " simultaneous multiplexed connections"); + connectionTable.put(idObj, info); + ++ numConnections; + } + + // inform remote endpoint of new connection + synchronized (dataOut) { + try { + dataOut.writeByte(OPEN); + dataOut.writeShort(id); + dataOut.flush(); + } catch (IOException e) { + multiplexLog.log(Log.BRIEF, "exception: ", e); + + shutDown(); + throw e; + } + } + + return new TCPConnection(channel, info.in, info.out); + } + + /** + * Shut down all connections and clean up. + */ + public void shutDown() + { + // inform all associated streams + synchronized (connectionTable) { + // return if multiplexer already officially dead + if (!alive) + return; + alive = false; + + Enumeration enum_ = connectionTable.elements(); + while (enum_.hasMoreElements()) { + MultiplexConnectionInfo info = + (MultiplexConnectionInfo) enum_.nextElement(); + info.in.disconnect(); + info.out.disconnect(); + } + connectionTable.clear(); + numConnections = 0; + } + + // close underlying connection, if possible (and not already done) + try { + in.close(); + } catch (IOException e) { + } + try { + out.close(); + } catch (IOException e) { + } + } + + /** + * Send request for more data on connection to remote endpoint. + * @param info connection information structure + * @param len number of more bytes that can be received + */ + void sendRequest(MultiplexConnectionInfo info, int len) throws IOException + { + synchronized (dataOut) { + if (alive && !info.closed) + try { + dataOut.writeByte(REQUEST); + dataOut.writeShort(info.id); + dataOut.writeInt(len); + dataOut.flush(); + } catch (IOException e) { + multiplexLog.log(Log.BRIEF, "exception: ", e); + + shutDown(); + throw e; + } + } + } + + /** + * Send packet of requested data on connection to remote endpoint. + * @param info connection information structure + * @param buf array containg bytes to send + * @param off offset of first array index of packet + * @param len number of bytes in packet to send + */ + void sendTransmit(MultiplexConnectionInfo info, + byte buf[], int off, int len) throws IOException + { + synchronized (dataOut) { + if (alive && !info.closed) + try { + dataOut.writeByte(TRANSMIT); + dataOut.writeShort(info.id); + dataOut.writeInt(len); + dataOut.write(buf, off, len); + dataOut.flush(); + } catch (IOException e) { + multiplexLog.log(Log.BRIEF, "exception: ", e); + + shutDown(); + throw e; + } + } + } + + /** + * Inform remote endpoint that connection has been closed. + * @param info connection information structure + */ + void sendClose(MultiplexConnectionInfo info) throws IOException + { + info.out.disconnect(); + synchronized (dataOut) { + if (alive && !info.closed) + try { + dataOut.writeByte(CLOSE); + dataOut.writeShort(info.id); + dataOut.flush(); + info.closed = true; + } catch (IOException e) { + multiplexLog.log(Log.BRIEF, "exception: ", e); + + shutDown(); + throw e; + } + } + } + + /** + * Acknowledge remote endpoint's closing of connection. + * @param info connection information structure + */ + void sendCloseAck(MultiplexConnectionInfo info) throws IOException + { + synchronized (dataOut) { + if (alive && !info.closed) + try { + dataOut.writeByte(CLOSEACK); + dataOut.writeShort(info.id); + dataOut.flush(); + info.closed = true; + } catch (IOException e) { + multiplexLog.log(Log.BRIEF, "exception: ", e); + + shutDown(); + throw e; + } + } + } + + /** + * Shut down connection upon finalization. + */ + protected void finalize() throws Throwable + { + super.finalize(); + shutDown(); + } +} diff --git a/src/share/classes/sun/rmi/transport/tcp/MultiplexConnectionInfo.java b/src/share/classes/sun/rmi/transport/tcp/MultiplexConnectionInfo.java new file mode 100644 index 000000000..3bc3efefa --- /dev/null +++ b/src/share/classes/sun/rmi/transport/tcp/MultiplexConnectionInfo.java @@ -0,0 +1,55 @@ +/* + * Copyright 1996 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ +package sun.rmi.transport.tcp; + +/** + * MultiplexConnectionInfo groups related information about a + * virtual connection managed by a ConnectionMultiplexer object. + * + * @author Peter Jones + */ +class MultiplexConnectionInfo { + + /** integer that uniquely identifies this connection */ + int id; + + /** input stream for reading from connection */ + MultiplexInputStream in = null; + + /** output stream for writing to connection */ + MultiplexOutputStream out = null; + + /** true if this connection has been closed */ + boolean closed = false; + + /** + * Create information structure for given connection identifier. + * @param id connection identifier + */ + MultiplexConnectionInfo(int id) + { + this.id = id; + } +} diff --git a/src/share/classes/sun/rmi/transport/tcp/MultiplexInputStream.java b/src/share/classes/sun/rmi/transport/tcp/MultiplexInputStream.java new file mode 100644 index 000000000..1c302050b --- /dev/null +++ b/src/share/classes/sun/rmi/transport/tcp/MultiplexInputStream.java @@ -0,0 +1,213 @@ +/* + * Copyright 1996-1997 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ +package sun.rmi.transport.tcp; + +import java.io.*; + +/** + * MultiplexInputStream manages receiving data over a connection managed + * by a ConnectionMultiplexer object. This object is responsible for + * requesting more bytes of data as space in its internal buffer becomes + * available. + * + * @author Peter Jones + */ +final class MultiplexInputStream extends InputStream { + + /** object managing multiplexed connection */ + private ConnectionMultiplexer manager; + + /** information about the connection this is the input stream for */ + private MultiplexConnectionInfo info; + + /** input buffer */ + private byte buffer[]; + + /** number of real data bytes present in buffer */ + private int present = 0; + + /** current position to read from in input buffer */ + private int pos = 0; + + /** pending number of bytes this stream has requested */ + private int requested = 0; + + /** true if this connection has been disconnected */ + private boolean disconnected = false; + + /** + * lock acquired to access shared variables: + * buffer, present, pos, requested, & disconnected + * WARNING: Any of the methods manager.send*() should not be + * invoked while this lock is held, since they could potentially + * block if the underlying connection's transport buffers are + * full, and the manager may need to acquire this lock to process + * and consume data coming over the underlying connection. + */ + private Object lock = new Object(); + + /** level at which more data is requested when read past */ + private int waterMark; + + /** data structure for holding reads of one byte */ + private byte temp[] = new byte[1]; + + /** + * Create a new MultiplexInputStream for the given manager. + * @param manager object that manages this connection + * @param info structure for connection this stream reads from + * @param bufferLength length of input buffer + */ + MultiplexInputStream( + ConnectionMultiplexer manager, + MultiplexConnectionInfo info, + int bufferLength) + { + this.manager = manager; + this.info = info; + + buffer = new byte[bufferLength]; + waterMark = bufferLength / 2; + } + + /** + * Read a byte from the connection. + */ + public synchronized int read() throws IOException + { + int n = read(temp, 0, 1); + if (n != 1) + return -1; + return temp[0] & 0xFF; + } + + /** + * Read a subarray of bytes from connection. This method blocks for + * at least one byte, and it returns the number of bytes actually read, + * or -1 if the end of the stream was detected. + * @param b array to read bytes into + * @param off offset of beginning of bytes to read into + * @param len number of bytes to read + */ + public synchronized int read(byte b[], int off, int len) throws IOException + { + if (len <= 0) + return 0; + + int moreSpace; + synchronized (lock) { + if (pos >= present) + pos = present = 0; + else if (pos >= waterMark) { + System.arraycopy(buffer, pos, buffer, 0, present - pos); + present -= pos; + pos = 0; + } + int freeSpace = buffer.length - present; + moreSpace = Math.max(freeSpace - requested, 0); + } + if (moreSpace > 0) + manager.sendRequest(info, moreSpace); + synchronized (lock) { + requested += moreSpace; + while ((pos >= present) && !disconnected) { + try { + lock.wait(); + } catch (InterruptedException e) { + } + } + if (disconnected && pos >= present) + return -1; + + int available = present - pos; + if (len < available) { + System.arraycopy(buffer, pos, b, off, len); + pos += len; + return len; + } + else { + System.arraycopy(buffer, pos, b, off, available); + pos = present = 0; + // could send another request here, if len > available?? + return available; + } + } + } + + /** + * Return the number of bytes immediately available for reading. + */ + public int available() throws IOException + { + synchronized (lock) { + return present - pos; + } + } + + /** + * Close this connection. + */ + public void close() throws IOException + { + manager.sendClose(info); + } + + /** + * Receive bytes transmitted from connection at remote endpoint. + * @param length number of bytes transmitted + * @param in input stream with those bytes ready to be read + */ + void receive(int length, DataInputStream in) + throws IOException + { + /* TO DO: Optimize so that data received from stream can be loaded + * directly into user's buffer if there is a pending read(). + */ + synchronized (lock) { + if ((pos > 0) && ((buffer.length - present) < length)) { + System.arraycopy(buffer, pos, buffer, 0, present - pos); + present -= pos; + pos = 0; + } + if ((buffer.length - present) < length) + throw new IOException("Receive buffer overflow"); + in.readFully(buffer, present, length); + present += length; + requested -= length; + lock.notifyAll(); + } + } + + /** + * Disconnect this stream from all connection activity. + */ + void disconnect() + { + synchronized (lock) { + disconnected = true; + lock.notifyAll(); + } + } +} diff --git a/src/share/classes/sun/rmi/transport/tcp/MultiplexOutputStream.java b/src/share/classes/sun/rmi/transport/tcp/MultiplexOutputStream.java new file mode 100644 index 000000000..d7ef40837 --- /dev/null +++ b/src/share/classes/sun/rmi/transport/tcp/MultiplexOutputStream.java @@ -0,0 +1,231 @@ +/* + * Copyright 1996 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ +package sun.rmi.transport.tcp; + +import java.io.*; + +/** + * MultiplexOutputStream manages sending data over a conection managed + * by a ConnectionMultiplexer object. Data written is buffered until the + * internal buffer is full or the flush() method is called, at which + * point it attempts to push a packet of bytes through to the remote + * endpoint. This will never push more bytes than the amount already + * requested by the remote endpoint (to prevent receive buffer from + * overflowing), so if the write() and flush() methods will block + * until their operation can complete if enough bytes cannot be + * pushed immediately. + * + * @author Peter Jones + */ +final class MultiplexOutputStream extends OutputStream { + + /** object managing multiplexed connection */ + private ConnectionMultiplexer manager; + + /** information about the connection this is the output stream for */ + private MultiplexConnectionInfo info; + + /** output buffer */ + private byte buffer[]; + + /** current position to write to in output buffer */ + private int pos = 0; + + /** pending number of bytes requested by remote endpoint */ + private int requested = 0; + + /** true if this connection has been disconnected */ + private boolean disconnected = false; + + /** + * lock acquired to access shared variables: + * requested & disconnected + * WARNING: Any of the methods manager.send*() should not be + * invoked while this lock is held, since they could potentially + * block if the underlying connection's transport buffers are + * full, and the manager may need to acquire this lock to process + * and consume data coming over the underlying connection. + */ + private Object lock = new Object(); + + /** + * Create a new MultiplexOutputStream for the given manager. + * @param manager object that manages this connection + * @param info structure for connection this stream writes to + * @param bufferLength length of output buffer + */ + MultiplexOutputStream( + ConnectionMultiplexer manager, + MultiplexConnectionInfo info, + int bufferLength) + { + this.manager = manager; + this.info = info; + + buffer = new byte[bufferLength]; + pos = 0; + } + + /** + * Write a byte over connection. + * @param b byte of data to write + */ + public synchronized void write(int b) throws IOException + { + while (pos >= buffer.length) + push(); + buffer[pos ++] = (byte) b; + } + + /** + * Write a subarray of bytes over connection. + * @param b array containing bytes to write + * @param off offset of beginning of bytes to write + * @param len number of bytes to write + */ + public synchronized void write(byte b[], int off, int len) + throws IOException + { + if (len <= 0) + return; + + // if enough free space in output buffer, just copy into there + int freeSpace = buffer.length - pos; + if (len <= freeSpace) { + System.arraycopy(b, off, buffer, pos, len); + pos += len; + return; + } + + // else, flush buffer and send rest directly to avoid array copy + flush(); + int local_requested; + while (true) { + synchronized (lock) { + while ((local_requested = requested) < 1 && !disconnected) { + try { + lock.wait(); + } catch (InterruptedException e) { + } + } + if (disconnected) + throw new IOException("Connection closed"); + } + + if (local_requested < len) { + manager.sendTransmit(info, b, off, local_requested); + off += local_requested; + len -= local_requested; + synchronized (lock) { + requested -= local_requested; + } + } + else { + manager.sendTransmit(info, b, off, len); + synchronized (lock) { + requested -= len; + } + // len = 0; + break; + } + } + } + + /** + * Guarantee that all data written to this stream has been pushed + * over and made available to the remote endpoint. + */ + public synchronized void flush() throws IOException { + while (pos > 0) + push(); + } + + /** + * Close this connection. + */ + public void close() throws IOException + { + manager.sendClose(info); + } + + /** + * Take note of more bytes requested by conection at remote endpoint. + * @param num number of additional bytes requested + */ + void request(int num) + { + synchronized (lock) { + requested += num; + lock.notifyAll(); + } + } + + /** + * Disconnect this stream from all connection activity. + */ + void disconnect() + { + synchronized (lock) { + disconnected = true; + lock.notifyAll(); + } + } + + /** + * Push bytes in output buffer to connection at remote endpoint. + * This method blocks until at least one byte has been pushed across. + */ + private void push() throws IOException + { + int local_requested; + synchronized (lock) { + while ((local_requested = requested) < 1 && !disconnected) { + try { + lock.wait(); + } catch (InterruptedException e) { + } + } + if (disconnected) + throw new IOException("Connection closed"); + } + + if (local_requested < pos) { + manager.sendTransmit(info, buffer, 0, local_requested); + System.arraycopy(buffer, local_requested, + buffer, 0, pos - local_requested); + pos -= local_requested; + synchronized (lock) { + requested -= local_requested; + } + } + else { + manager.sendTransmit(info, buffer, 0, pos); + synchronized (lock) { + requested -= pos; + } + pos = 0; + } + } +} diff --git a/src/share/classes/sun/rmi/transport/tcp/TCPChannel.java b/src/share/classes/sun/rmi/transport/tcp/TCPChannel.java new file mode 100644 index 000000000..f426012bf --- /dev/null +++ b/src/share/classes/sun/rmi/transport/tcp/TCPChannel.java @@ -0,0 +1,536 @@ +/* + * Copyright 1996-2005 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ +package sun.rmi.transport.tcp; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.lang.ref.Reference; +import java.lang.ref.SoftReference; +import java.net.Socket; +import java.rmi.ConnectIOException; +import java.rmi.RemoteException; +import java.security.AccessControlContext; +import java.security.AccessController; +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; +import java.util.WeakHashMap; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import sun.rmi.runtime.Log; +import sun.rmi.runtime.NewThreadAction; +import sun.rmi.runtime.RuntimeUtil; +import sun.rmi.transport.Channel; +import sun.rmi.transport.Connection; +import sun.rmi.transport.Endpoint; +import sun.rmi.transport.TransportConstants; +import sun.security.action.GetIntegerAction; +import sun.security.action.GetLongAction; + +/** + * TCPChannel is the socket-based implementation of the RMI Channel + * abstraction. + * + * @author Ann Wollrath + */ +public class TCPChannel implements Channel { + /** endpoint for this channel */ + private final TCPEndpoint ep; + /** transport for this channel */ + private final TCPTransport tr; + /** list of cached connections */ + private final List<TCPConnection> freeList = + new ArrayList<TCPConnection>(); + /** frees cached connections that have expired (guarded by freeList) */ + private Future<?> reaper = null; + + /** using multiplexer (for bi-directional applet communication */ + private boolean usingMultiplexer = false; + /** connection multiplexer, if used */ + private ConnectionMultiplexer multiplexer = null; + /** connection acceptor (should be in TCPTransport) */ + private ConnectionAcceptor acceptor; + + /** most recently authorized AccessControlContext */ + private AccessControlContext okContext; + + /** cache of authorized AccessControlContexts */ + private WeakHashMap<AccessControlContext, + Reference<AccessControlContext>> authcache; + + /** the SecurityManager which authorized okContext and authcache */ + private SecurityManager cacheSecurityManager = null; + + /** client-side connection idle usage timeout */ + private static final long idleTimeout = // default 15 seconds + AccessController.doPrivileged( + new GetLongAction("sun.rmi.transport.connectionTimeout", 15000)); + + /** client-side connection handshake read timeout */ + private static final int handshakeTimeout = // default 1 minute + AccessController.doPrivileged( + new GetIntegerAction("sun.rmi.transport.tcp.handshakeTimeout", + 60000)); + + /** client-side connection response read timeout (after handshake) */ + private static final int responseTimeout = // default infinity + AccessController.doPrivileged( + new GetIntegerAction("sun.rmi.transport.tcp.responseTimeout", 0)); + + /** thread pool for scheduling delayed tasks */ + private static final ScheduledExecutorService scheduler = + AccessController.doPrivileged( + new RuntimeUtil.GetInstanceAction()).getScheduler(); + + /** + * Create channel for endpoint. + */ + TCPChannel(TCPTransport tr, TCPEndpoint ep) { + this.tr = tr; + this.ep = ep; + } + + /** + * Return the endpoint for this channel. + */ + public Endpoint getEndpoint() { + return ep; + } + + /** + * Checks if the current caller has sufficient privilege to make + * a connection to the remote endpoint. + * @exception SecurityException if caller is not allowed to use this + * Channel. + */ + private void checkConnectPermission() throws SecurityException { + SecurityManager security = System.getSecurityManager(); + if (security == null) + return; + + if (security != cacheSecurityManager) { + // The security manager changed: flush the cache + okContext = null; + authcache = new WeakHashMap<AccessControlContext, + Reference<AccessControlContext>>(); + cacheSecurityManager = security; + } + + AccessControlContext ctx = AccessController.getContext(); + + // If ctx is the same context as last time, or if it + // appears in the cache, bypass the checkConnect. + if (okContext == null || + !(okContext.equals(ctx) || authcache.containsKey(ctx))) + { + security.checkConnect(ep.getHost(), ep.getPort()); + authcache.put(ctx, new SoftReference<AccessControlContext>(ctx)); + // A WeakHashMap is transformed into a SoftHashSet by making + // each value softly refer to its own key (Peter's idea). + } + okContext = ctx; + } + + /** + * Supplies a connection to the endpoint of the address space + * for which this is a channel. The returned connection may + * be one retrieved from a cache of idle connections. + */ + public Connection newConnection() throws RemoteException { + TCPConnection conn; + + // loop until we find a free live connection (in which case + // we return) or until we run out of freelist (in which case + // the loop exits) + do { + conn = null; + // try to get a free connection + synchronized (freeList) { + int elementPos = freeList.size()-1; + + if (elementPos >= 0) { + // If there is a security manager, make sure + // the caller is allowed to connect to the + // requested endpoint. + checkConnectPermission(); + conn = freeList.get(elementPos); + freeList.remove(elementPos); + } + } + + // at this point, conn is null iff the freelist is empty, + // and nonnull if a free connection of uncertain vitality + // has been found. + + if (conn != null) { + // check to see if the connection has closed since last use + if (!conn.isDead()) { + TCPTransport.tcpLog.log(Log.BRIEF, "reuse connection"); + return conn; + } + + // conn is dead, and cannot be reused (reuse => false) + this.free(conn, false); + } + } while (conn != null); + + // none free, so create a new connection + return (createConnection()); + } + + /** + * Create a new connection to the remote endpoint of this channel. + * The returned connection is new. The caller must already have + * passed a security checkConnect or equivalent. + */ + private Connection createConnection() throws RemoteException { + Connection conn; + + TCPTransport.tcpLog.log(Log.BRIEF, "create connection"); + + if (!usingMultiplexer) { + Socket sock = ep.newSocket(); + conn = new TCPConnection(this, sock); + + try { + DataOutputStream out = + new DataOutputStream(conn.getOutputStream()); + writeTransportHeader(out); + + // choose protocol (single op if not reusable socket) + if (!conn.isReusable()) { + out.writeByte(TransportConstants.SingleOpProtocol); + } else { + out.writeByte(TransportConstants.StreamProtocol); + out.flush(); + + /* + * Set socket read timeout to configured value for JRMP + * connection handshake; this also serves to guard against + * non-JRMP servers that do not respond (see 4322806). + */ + int originalSoTimeout = 0; + try { + originalSoTimeout = sock.getSoTimeout(); + sock.setSoTimeout(handshakeTimeout); + } catch (Exception e) { + // if we fail to set this, ignore and proceed anyway + } + + DataInputStream in = + new DataInputStream(conn.getInputStream()); + byte ack = in.readByte(); + if (ack != TransportConstants.ProtocolAck) { + throw new ConnectIOException( + ack == TransportConstants.ProtocolNack ? + "JRMP StreamProtocol not supported by server" : + "non-JRMP server at remote endpoint"); + } + + String suggestedHost = in.readUTF(); + int suggestedPort = in.readInt(); + if (TCPTransport.tcpLog.isLoggable(Log.VERBOSE)) { + TCPTransport.tcpLog.log(Log.VERBOSE, + "server suggested " + suggestedHost + ":" + + suggestedPort); + } + + // set local host name, if unknown + TCPEndpoint.setLocalHost(suggestedHost); + // do NOT set the default port, because we don't + // know if we can't listen YET... + + // write out default endpoint to match protocol + // (but it serves no purpose) + TCPEndpoint localEp = + TCPEndpoint.getLocalEndpoint(0, null, null); + out.writeUTF(localEp.getHost()); + out.writeInt(localEp.getPort()); + if (TCPTransport.tcpLog.isLoggable(Log.VERBOSE)) { + TCPTransport.tcpLog.log(Log.VERBOSE, "using " + + localEp.getHost() + ":" + localEp.getPort()); + } + + /* + * After JRMP handshake, set socket read timeout to value + * configured for the rest of the lifetime of the + * connection. NOTE: this timeout, if configured to a + * finite duration, places an upper bound on the time + * that a remote method call is permitted to execute. + */ + try { + /* + * If socket factory had set a non-zero timeout on its + * own, then restore it instead of using the property- + * configured value. + */ + sock.setSoTimeout((originalSoTimeout != 0 ? + originalSoTimeout : + responseTimeout)); + } catch (Exception e) { + // if we fail to set this, ignore and proceed anyway + } + + out.flush(); + } + } catch (IOException e) { + if (e instanceof RemoteException) + throw (RemoteException) e; + else + throw new ConnectIOException( + "error during JRMP connection establishment", e); + } + } else { + try { + conn = multiplexer.openConnection(); + } catch (IOException e) { + synchronized (this) { + usingMultiplexer = false; + multiplexer = null; + } + throw new ConnectIOException( + "error opening virtual connection " + + "over multiplexed connection", e); + } + } + return conn; + } + + /** + * Free the connection generated by this channel. + * @param conn The connection + * @param reuse If true, the connection is in a state in which it + * can be reused for another method call. + */ + public void free(Connection conn, boolean reuse) { + if (conn == null) return; + + if (reuse && conn.isReusable()) { + long lastuse = System.currentTimeMillis(); + TCPConnection tcpConnection = (TCPConnection) conn; + + TCPTransport.tcpLog.log(Log.BRIEF, "reuse connection"); + + /* + * Cache connection; if reaper task for expired + * connections isn't scheduled, then schedule it. + */ + synchronized (freeList) { + freeList.add(tcpConnection); + if (reaper == null) { + TCPTransport.tcpLog.log(Log.BRIEF, "create reaper"); + + reaper = scheduler.scheduleWithFixedDelay( + new Runnable() { + public void run() { + TCPTransport.tcpLog.log(Log.VERBOSE, + "wake up"); + freeCachedConnections(); + } + }, idleTimeout, idleTimeout, TimeUnit.MILLISECONDS); + } + } + + tcpConnection.setLastUseTime(lastuse); + tcpConnection.setExpiration(lastuse + idleTimeout); + } else { + TCPTransport.tcpLog.log(Log.BRIEF, "close connection"); + + try { + conn.close(); + } catch (IOException ignored) { + } + } + } + + /** + * Send transport header over stream. + */ + private void writeTransportHeader(DataOutputStream out) + throws RemoteException + { + try { + // write out transport header + DataOutputStream dataOut = + new DataOutputStream(out); + dataOut.writeInt(TransportConstants.Magic); + dataOut.writeShort(TransportConstants.Version); + } catch (IOException e) { + throw new ConnectIOException( + "error writing JRMP transport header", e); + } + } + + /** + * Use given connection multiplexer object to obtain new connections + * through this channel. + */ + synchronized void useMultiplexer(ConnectionMultiplexer newMultiplexer) { + // for now, always just use the last one given + multiplexer = newMultiplexer; + + usingMultiplexer = true; + } + + /** + * Accept a connection provided over a multiplexed channel. + */ + void acceptMultiplexConnection(Connection conn) { + if (acceptor == null) { + acceptor = new ConnectionAcceptor(tr); + acceptor.startNewAcceptor(); + } + acceptor.accept(conn); + } + + /** + * Closes all the connections in the cache, whether timed out or not. + */ + public void shedCache() { + // Build a list of connections, to avoid holding the freeList + // lock during (potentially long-running) close() calls. + Connection[] conn; + synchronized (freeList) { + conn = freeList.toArray(new Connection[freeList.size()]); + freeList.clear(); + } + + // Close all the connections that were free + for (int i = conn.length; --i >= 0; ) { + Connection c = conn[i]; + conn[i] = null; // help gc + try { + c.close(); + } catch (java.io.IOException e) { + // eat exception + } + } + } + + private void freeCachedConnections() { + /* + * Remove each connection whose time out has expired. + */ + synchronized (freeList) { + int size = freeList.size(); + + if (size > 0) { + long time = System.currentTimeMillis(); + ListIterator<TCPConnection> iter = freeList.listIterator(size); + + while (iter.hasPrevious()) { + TCPConnection conn = iter.previous(); + if (conn.expired(time)) { + TCPTransport.tcpLog.log(Log.VERBOSE, + "connection timeout expired"); + + try { + conn.close(); + } catch (java.io.IOException e) { + // eat exception + } + iter.remove(); + } + } + } + + if (freeList.isEmpty()) { + reaper.cancel(false); + reaper = null; + } + } + } +} + +/** + * ConnectionAcceptor manages accepting new connections and giving them + * to TCPTransport's message handler on new threads. + * + * Since this object only needs to know which transport to give new + * connections to, it doesn't need to be per-channel as currently + * implemented. + */ +class ConnectionAcceptor implements Runnable { + + /** transport that will handle message on accepted connections */ + private TCPTransport transport; + + /** queue of connections to be accepted */ + private List<Connection> queue = new ArrayList<Connection>(); + + /** thread ID counter */ + private static int threadNum = 0; + + /** + * Create a new ConnectionAcceptor that will give connections + * to the specified transport on a new thread. + */ + public ConnectionAcceptor(TCPTransport transport) { + this.transport = transport; + } + + /** + * Start a new thread to accept connections. + */ + public void startNewAcceptor() { + Thread t = AccessController.doPrivileged( + new NewThreadAction(ConnectionAcceptor.this, + "Multiplex Accept-" + ++ threadNum, + true)); + t.start(); + } + + /** + * Add connection to queue of connections to be accepted. + */ + public void accept(Connection conn) { + synchronized (queue) { + queue.add(conn); + queue.notify(); + } + } + + /** + * Give transport next accepted conection, when available. + */ + public void run() { + Connection conn; + + synchronized (queue) { + while (queue.size() == 0) { + try { + queue.wait(); + } catch (InterruptedException e) { + } + } + startNewAcceptor(); + conn = queue.remove(0); + } + + transport.handleMessages(conn, true); + } +} diff --git a/src/share/classes/sun/rmi/transport/tcp/TCPConnection.java b/src/share/classes/sun/rmi/transport/tcp/TCPConnection.java new file mode 100644 index 000000000..ac1971590 --- /dev/null +++ b/src/share/classes/sun/rmi/transport/tcp/TCPConnection.java @@ -0,0 +1,236 @@ +/* + * Copyright 1996-2001 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.transport.tcp; + +import java.io.*; +import java.net.InetAddress; +import java.net.Socket; +import java.net.SocketException; +import java.rmi.*; +import java.rmi.server.RMISocketFactory; +import sun.rmi.runtime.Log; +import sun.rmi.transport.*; +import sun.rmi.transport.proxy.*; + +public class TCPConnection implements Connection { + + private Socket socket; + private Channel channel; + private InputStream in = null; + private OutputStream out = null; + private long expiration = Long.MAX_VALUE; + private long lastuse = Long.MIN_VALUE; + private long roundtrip = 5; // round-trip time for ping + + /** + * Constructor used for creating a connection to accept call + * (an input connection) + */ + TCPConnection(TCPChannel ch, Socket s, InputStream in, OutputStream out) + { + socket = s; + channel = ch; + this.in = in; + this.out = out; + } + + /** + * Constructor used by subclass when underlying input and output streams + * are already available. + */ + TCPConnection(TCPChannel ch, InputStream in, OutputStream out) + { + this(ch, null, in, out); + } + + /** + * Constructor used when socket is available, but not underlying + * streams. + */ + TCPConnection(TCPChannel ch, Socket s) + { + this(ch, s, null, null); + } + + /** + * Gets the output stream for this connection + */ + public OutputStream getOutputStream() throws IOException + { + if (out == null) + out = new BufferedOutputStream(socket.getOutputStream()); + return out; + } + + /** + * Release the output stream for this connection. + */ + public void releaseOutputStream() throws IOException + { + if (out != null) + out.flush(); + } + + /** + * Gets the input stream for this connection. + */ + public InputStream getInputStream() throws IOException + { + if (in == null) + in = new BufferedInputStream(socket.getInputStream()); + return in; + } + + + /** + * Release the input stream for this connection. + */ + public void releaseInputStream() + { + } + + /** + * Determine if this connection can be used for multiple operations. + * If the socket implements RMISocketInfo, then we can query it about + * this; otherwise, assume that it does provide a full-duplex + * persistent connection like java.net.Socket. + */ + public boolean isReusable() + { + if ((socket != null) && (socket instanceof RMISocketInfo)) + return ((RMISocketInfo) socket).isReusable(); + else + return true; + } + + /** + * Set the expiration time of this connection. + * @param time The time at which the time out expires. + */ + void setExpiration(long time) + { + expiration = time; + } + + /** + * Set the timestamp at which this connection was last used successfully. + * The connection will be pinged for liveness if reused long after + * this time. + * @param time The time at which the connection was last active. + */ + void setLastUseTime(long time) + { + lastuse = time; + } + + /** + * Returns true if the timeout has expired on this connection; + * otherwise returns false. + * @param time The current time. + */ + boolean expired(long time) + { + return expiration <= time; + } + + /** + * Probes the connection to see if it still alive and connected to + * a responsive server. If the connection has been idle for too + * long, the server is pinged. ``Too long'' means ``longer than the + * last ping round-trip time''. + * <P> + * This method may misdiagnose a dead connection as live, but it + * will never misdiagnose a live connection as dead. + * @return true if the connection and server are recently alive + */ + public boolean isDead() + { + InputStream i; + OutputStream o; + + // skip ping if recently used within 1 RTT + long start = System.currentTimeMillis(); + if ((roundtrip > 0) && (start < lastuse + roundtrip)) + return (false); // still alive and warm + + // Get the streams + try { + i = getInputStream(); + o = getOutputStream(); + } catch (IOException e) { + return (true); // can't even get a stream, must be very dead + } + + // Write the ping byte and read the reply byte + int response = 0; + try { + o.write(TransportConstants.Ping); + o.flush(); + response = i.read(); + } catch (IOException ex) { + TCPTransport.tcpLog.log(Log.VERBOSE, "exception: ", ex); + TCPTransport.tcpLog.log(Log.BRIEF, "server ping failed"); + + return (true); // server failed the ping test + } + + if (response == TransportConstants.PingAck) { + // save most recent RTT for future use + roundtrip = (System.currentTimeMillis() - start) * 2; + // clock-correction may make roundtrip < 0; doesn't matter + return (false); // it's alive and 5-by-5 + } + + if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) { + TCPTransport.tcpLog.log(Log.BRIEF, + (response == -1 ? "server has been deactivated" : + "server protocol error: ping response = " + response)); + } + return (true); + } + + /** + * Close the connection. */ + public void close() throws IOException + { + TCPTransport.tcpLog.log(Log.BRIEF, "close connection"); + + if (socket != null) + socket.close(); + else { + in.close(); + out.close(); + } + } + + /** + * Returns the channel for this connection. + */ + public Channel getChannel() + { + return channel; + } +} diff --git a/src/share/classes/sun/rmi/transport/tcp/TCPEndpoint.java b/src/share/classes/sun/rmi/transport/tcp/TCPEndpoint.java new file mode 100644 index 000000000..835a7c7cd --- /dev/null +++ b/src/share/classes/sun/rmi/transport/tcp/TCPEndpoint.java @@ -0,0 +1,787 @@ +/* + * Copyright 1996-2005 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ +package sun.rmi.transport.tcp; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.rmi.ConnectIOException; +import java.rmi.RemoteException; +import java.rmi.server.RMIClientSocketFactory; +import java.rmi.server.RMIServerSocketFactory; +import java.rmi.server.RMISocketFactory; +import java.security.AccessController; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Map; +import java.util.Set; +import sun.rmi.runtime.Log; +import sun.rmi.runtime.NewThreadAction; +import sun.rmi.transport.Channel; +import sun.rmi.transport.Endpoint; +import sun.rmi.transport.Target; +import sun.rmi.transport.Transport; +import sun.security.action.GetBooleanAction; +import sun.security.action.GetIntegerAction; +import sun.security.action.GetPropertyAction; + +/** + * TCPEndpoint represents some communication endpoint for an address + * space (VM). + * + * @author Ann Wollrath + */ +public class TCPEndpoint implements Endpoint { + /** IP address or host name */ + private String host; + /** port number */ + private int port; + /** custom client socket factory (null if not custom factory) */ + private final RMIClientSocketFactory csf; + /** custom server socket factory (null if not custom factory) */ + private final RMIServerSocketFactory ssf; + + /** if local, the port number to listen on */ + private int listenPort = -1; + /** if local, the transport object associated with this endpoint */ + private TCPTransport transport = null; + + /** the local host name */ + private static String localHost; + /** true if real local host name is known yet */ + private static boolean localHostKnown; + + // this should be a *private* method since it is privileged + private static int getInt(String name, int def) { + return AccessController.doPrivileged(new GetIntegerAction(name, def)); + } + + // this should be a *private* method since it is privileged + private static boolean getBoolean(String name) { + return AccessController.doPrivileged(new GetBooleanAction(name)); + } + + /** + * Returns the value of the java.rmi.server.hostname property. + */ + private static String getHostnameProperty() { + return AccessController.doPrivileged( + new GetPropertyAction("java.rmi.server.hostname")); + } + + /** + * Find host name of local machine. Property "java.rmi.server.hostname" + * is used if set, so server administrator can compensate for the possible + * inablility to get fully qualified host name from VM. + */ + static { + localHostKnown = true; + localHost = getHostnameProperty(); + + // could try querying CGI program here? + if (localHost == null) { + try { + InetAddress localAddr = InetAddress.getLocalHost(); + byte[] raw = localAddr.getAddress(); + if ((raw[0] == 127) && + (raw[1] == 0) && + (raw[2] == 0) && + (raw[3] == 1)) { + localHostKnown = false; + } + + /* if the user wishes to use a fully qualified domain + * name then attempt to find one. + */ + if (getBoolean("java.rmi.server.useLocalHostName")) { + localHost = FQDN.attemptFQDN(localAddr); + } else { + /* default to using ip addresses, names will + * work across seperate domains. + */ + localHost = localAddr.getHostAddress(); + } + } catch (Exception e) { + localHostKnown = false; + localHost = null; + } + } + + if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) { + TCPTransport.tcpLog.log(Log.BRIEF, + "localHostKnown = " + localHostKnown + + ", localHost = " + localHost); + } + } + + /** maps an endpoint key containing custom socket factories to + * their own unique endpoint */ + // TBD: should this be a weak hash table? + private static final + Map<TCPEndpoint,LinkedList<TCPEndpoint>> localEndpoints = + new HashMap<TCPEndpoint,LinkedList<TCPEndpoint>>(); + + /** + * Create an endpoint for a specified host and port. + * This should not be used by external classes to create endpoints + * for servers in this VM; use getLocalEndpoint instead. + */ + public TCPEndpoint(String host, int port) { + this(host, port, null, null); + } + + /** + * Create a custom socket factory endpoint for a specified host and port. + * This should not be used by external classes to create endpoints + * for servers in this VM; use getLocalEndpoint instead. + */ + public TCPEndpoint(String host, int port, RMIClientSocketFactory csf, + RMIServerSocketFactory ssf) + { + if (host == null) + host = ""; + this.host = host; + this.port = port; + this.csf = csf; + this.ssf = ssf; + } + + /** + * Get an endpoint for the local address space on specified port. + * If port number is 0, it returns shared default endpoint object + * whose host name and port may or may not have been determined. + */ + public static TCPEndpoint getLocalEndpoint(int port) { + return getLocalEndpoint(port, null, null); + } + + public static TCPEndpoint getLocalEndpoint(int port, + RMIClientSocketFactory csf, + RMIServerSocketFactory ssf) + { + /* + * Find mapping for an endpoint key to the list of local unique + * endpoints for this client/server socket factory pair (perhaps + * null) for the specific port. + */ + TCPEndpoint ep = null; + + synchronized (localEndpoints) { + TCPEndpoint endpointKey = new TCPEndpoint(null, port, csf, ssf); + LinkedList<TCPEndpoint> epList = localEndpoints.get(endpointKey); + String localHost = resampleLocalHost(); + + if (epList == null) { + /* + * Create new endpoint list. + */ + ep = new TCPEndpoint(localHost, port, csf, ssf); + epList = new LinkedList<TCPEndpoint>(); + epList.add(ep); + ep.listenPort = port; + ep.transport = new TCPTransport(epList); + localEndpoints.put(endpointKey, epList); + + if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) { + TCPTransport.tcpLog.log(Log.BRIEF, + "created local endpoint for socket factory " + ssf + + " on port " + port); + } + } else { + synchronized (epList) { + ep = epList.getLast(); + String lastHost = ep.host; + int lastPort = ep.port; + TCPTransport lastTransport = ep.transport; + // assert (localHost == null ^ lastHost != null) + if (localHost != null && !localHost.equals(lastHost)) { + /* + * Hostname has been updated; add updated endpoint + * to list. + */ + if (lastPort != 0) { + /* + * Remove outdated endpoints only if the + * port has already been set on those endpoints. + */ + epList.clear(); + } + ep = new TCPEndpoint(localHost, lastPort, csf, ssf); + ep.listenPort = port; + ep.transport = lastTransport; + epList.add(ep); + } + } + } + } + + return ep; + } + + /** + * Resamples the local hostname and returns the possibly-updated + * local hostname. + */ + private static String resampleLocalHost() { + + String hostnameProperty = getHostnameProperty(); + + synchronized (localEndpoints) { + // assert(localHostKnown ^ (localHost == null)) + + if (hostnameProperty != null) { + if (!localHostKnown) { + /* + * If the local hostname is unknown, update ALL + * existing endpoints with the new hostname. + */ + setLocalHost(hostnameProperty); + } else if (!hostnameProperty.equals(localHost)) { + /* + * Only update the localHost field for reference + * in future endpoint creation. + */ + localHost = hostnameProperty; + + if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) { + TCPTransport.tcpLog.log(Log.BRIEF, + "updated local hostname to: " + localHost); + } + } + } + return localHost; + } + } + + /** + * Set the local host name, if currently unknown. + */ + static void setLocalHost(String host) { + // assert (host != null) + + synchronized (localEndpoints) { + /* + * If host is not known, change the host field of ALL + * the local endpoints. + */ + if (!localHostKnown) { + localHost = host; + localHostKnown = true; + + if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) { + TCPTransport.tcpLog.log(Log.BRIEF, + "local host set to " + host); + } + for (LinkedList<TCPEndpoint> epList : localEndpoints.values()) + { + synchronized (epList) { + for (TCPEndpoint ep : epList) { + ep.host = host; + } + } + } + } + } + } + + /** + * Set the port of the (shared) default endpoint object. + * When first created, it contains port 0 because the transport + * hasn't tried to listen to get assigned a port, or if listening + * failed, a port hasn't been assigned from the server. + */ + static void setDefaultPort(int port, RMIClientSocketFactory csf, + RMIServerSocketFactory ssf) + { + TCPEndpoint endpointKey = new TCPEndpoint(null, 0, csf, ssf); + + synchronized (localEndpoints) { + LinkedList<TCPEndpoint> epList = localEndpoints.get(endpointKey); + + synchronized (epList) { + int size = epList.size(); + TCPEndpoint lastEp = epList.getLast(); + + for (TCPEndpoint ep : epList) { + ep.port = port; + } + if (size > 1) { + /* + * Remove all but the last element of the list + * (which contains the most recent hostname). + */ + epList.clear(); + epList.add(lastEp); + } + } + + /* + * Allow future exports to use the actual bound port + * explicitly (see 6269166). + */ + TCPEndpoint newEndpointKey = new TCPEndpoint(null, port, csf, ssf); + localEndpoints.put(newEndpointKey, epList); + + if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) { + TCPTransport.tcpLog.log(Log.BRIEF, + "default port for server socket factory " + ssf + + " and client socket factory " + csf + + " set to " + port); + } + } + } + + /** + * Returns transport for making connections to remote endpoints; + * (here, the default transport at port 0 is used). + */ + public Transport getOutboundTransport() { + TCPEndpoint localEndpoint = getLocalEndpoint(0, null, null); + return localEndpoint.transport; + } + + /** + * Returns the current list of known transports. + * The returned list is an unshared collection of Transports, + * including all transports which may have channels to remote + * endpoints. + */ + private static Collection<TCPTransport> allKnownTransports() { + // Loop through local endpoints, getting the transport of each one. + Set<TCPTransport> s; + synchronized (localEndpoints) { + // presize s to number of localEndpoints + s = new HashSet<TCPTransport>(localEndpoints.size()); + for (LinkedList<TCPEndpoint> epList : localEndpoints.values()) { + /* + * Each local endpoint has its transport added to s. + * Note: the transport is the same for all endpoints + * in the list, so it is okay to pick any one of them. + */ + TCPEndpoint ep = epList.getFirst(); + s.add(ep.transport); + } + } + return s; + } + + /** + * Release idle outbound connections to reduce demand on I/O resources. + * All transports are asked to release excess connections. + */ + public static void shedConnectionCaches() { + for (TCPTransport transport : allKnownTransports()) { + transport.shedConnectionCaches(); + } + } + + /** + * Export the object to accept incoming calls. + */ + public void exportObject(Target target) throws RemoteException { + transport.exportObject(target); + } + + /** + * Returns a channel for this (remote) endpoint. + */ + public Channel getChannel() { + return getOutboundTransport().getChannel(this); + } + + /** + * Returns address for endpoint + */ + public String getHost() { + return host; + } + + /** + * Returns the port for this endpoint. If this endpoint was + * created as a server endpoint (using getLocalEndpoint) for a + * default/anonymous port and its inbound transport has started + * listening, this method returns (instead of zero) the actual + * bound port suitable for passing to clients. + **/ + public int getPort() { + return port; + } + + /** + * Returns the port that this endpoint's inbound transport listens + * on, if this endpoint was created as a server endpoint (using + * getLocalEndpoint). If this endpoint was created for the + * default/anonymous port, then this method returns zero even if + * the transport has started listening. + **/ + public int getListenPort() { + return listenPort; + } + + /** + * Returns the transport for incoming connections to this + * endpoint, if this endpoint was created as a server endpoint + * (using getLocalEndpoint). + **/ + public Transport getInboundTransport() { + return transport; + } + + /** + * Get the client socket factory associated with this endpoint. + */ + public RMIClientSocketFactory getClientSocketFactory() { + return csf; + } + + /** + * Get the server socket factory associated with this endpoint. + */ + public RMIServerSocketFactory getServerSocketFactory() { + return ssf; + } + + /** + * Return string representation for endpoint. + */ + public String toString() { + return "[" + host + ":" + port + + (ssf != null ? "," + ssf : "") + + (csf != null ? "," + csf : "") + + "]"; + } + + public int hashCode() { + return port; + } + + public boolean equals(Object obj) { + if ((obj != null) && (obj instanceof TCPEndpoint)) { + TCPEndpoint ep = (TCPEndpoint) obj; + if (port != ep.port || !host.equals(ep.host)) + return false; + if (((csf == null) ^ (ep.csf == null)) || + ((ssf == null) ^ (ep.ssf == null))) + return false; + /* + * Fix for 4254510: perform socket factory *class* equality check + * before socket factory equality check to avoid passing + * a potentially naughty socket factory to this endpoint's + * {client,server} socket factory equals method. + */ + if ((csf != null) && + !(csf.getClass() == ep.csf.getClass() && csf.equals(ep.csf))) + return false; + if ((ssf != null) && + !(ssf.getClass() == ep.ssf.getClass() && ssf.equals(ep.ssf))) + return false; + return true; + } else { + return false; + } + } + + /* codes for the self-describing formats of wire representation */ + private static final int FORMAT_HOST_PORT = 0; + private static final int FORMAT_HOST_PORT_FACTORY = 1; + + /** + * Write endpoint to output stream. + */ + public void write(ObjectOutput out) throws IOException { + if (csf == null) { + out.writeByte(FORMAT_HOST_PORT); + out.writeUTF(host); + out.writeInt(port); + } else { + out.writeByte(FORMAT_HOST_PORT_FACTORY); + out.writeUTF(host); + out.writeInt(port); + out.writeObject(csf); + } + } + + /** + * Get the endpoint from the input stream. + * @param in the input stream + * @exception IOException If id could not be read (due to stream failure) + */ + public static TCPEndpoint read(ObjectInput in) + throws IOException, ClassNotFoundException + { + String host; + int port; + RMIClientSocketFactory csf = null; + + byte format = in.readByte(); + switch (format) { + case FORMAT_HOST_PORT: + host = in.readUTF(); + port = in.readInt(); + break; + + case FORMAT_HOST_PORT_FACTORY: + host = in.readUTF(); + port = in.readInt(); + csf = (RMIClientSocketFactory) in.readObject(); + break; + + default: + throw new IOException("invalid endpoint format"); + } + return new TCPEndpoint(host, port, csf, null); + } + + /** + * Write endpoint to output stream in older format used by + * UnicastRef for JDK1.1 compatibility. + */ + public void writeHostPortFormat(DataOutput out) throws IOException { + if (csf != null) { + throw new InternalError("TCPEndpoint.writeHostPortFormat: " + + "called for endpoint with non-null socket factory"); + } + out.writeUTF(host); + out.writeInt(port); + } + + /** + * Create a new endpoint from input stream data. + * @param in the input stream + */ + public static TCPEndpoint readHostPortFormat(DataInput in) + throws IOException + { + String host = in.readUTF(); + int port = in.readInt(); + return new TCPEndpoint(host, port); + } + + private static RMISocketFactory chooseFactory() { + RMISocketFactory sf = RMISocketFactory.getSocketFactory(); + if (sf == null) { + sf = TCPTransport.defaultSocketFactory; + } + return sf; + } + + /** + * Open and return new client socket connection to endpoint. + */ + Socket newSocket() throws RemoteException { + if (TCPTransport.tcpLog.isLoggable(Log.VERBOSE)) { + TCPTransport.tcpLog.log(Log.VERBOSE, + "opening socket to " + this); + } + + Socket socket; + + try { + RMIClientSocketFactory clientFactory = csf; + if (clientFactory == null) { + clientFactory = chooseFactory(); + } + socket = clientFactory.createSocket(host, port); + + } catch (java.net.UnknownHostException e) { + throw new java.rmi.UnknownHostException( + "Unknown host: " + host, e); + } catch (java.net.ConnectException e) { + throw new java.rmi.ConnectException( + "Connection refused to host: " + host, e); + } catch (IOException e) { + // We might have simply run out of file descriptors + try { + TCPEndpoint.shedConnectionCaches(); + // REMIND: should we retry createSocket? + } catch (OutOfMemoryError mem) { + // don't quit if out of memory + } catch (Exception ex) { + // don't quit if shed fails non-catastrophically + } + + throw new ConnectIOException("Exception creating connection to: " + + host, e); + } + + // set socket to disable Nagle's algorithm (always send immediately) + // TBD: should this be left up to socket factory instead? + try { + socket.setTcpNoDelay(true); + } catch (Exception e) { + // if we fail to set this, ignore and proceed anyway + } + + // fix 4187495: explicitly set SO_KEEPALIVE to prevent client hangs + try { + socket.setKeepAlive(true); + } catch (Exception e) { + // ignore and proceed + } + + return socket; + } + + /** + * Return new server socket to listen for connections on this endpoint. + */ + ServerSocket newServerSocket() throws IOException { + if (TCPTransport.tcpLog.isLoggable(Log.VERBOSE)) { + TCPTransport.tcpLog.log(Log.VERBOSE, + "creating server socket on " + this); + } + + RMIServerSocketFactory serverFactory = ssf; + if (serverFactory == null) { + serverFactory = chooseFactory(); + } + ServerSocket server = serverFactory.createServerSocket(listenPort); + + // if we listened on an anonymous port, set the default port + // (for this socket factory) + if (listenPort == 0) + setDefaultPort(server.getLocalPort(), csf, ssf); + + return server; + } + + /** + * The class FQDN encapsulates a routine that makes a best effort + * attempt to retrieve the fully qualified domain name of the local + * host. + * + * @author Laird Dornin + */ + private static class FQDN implements Runnable { + + /** + * strings in which we can store discovered fqdn + */ + private String reverseLookup; + + private String hostAddress; + + private FQDN(String hostAddress) { + this.hostAddress = hostAddress; + } + + /** + * Do our best to obtain a fully qualified hostname for the local + * host. Perform the following steps to get a localhostname: + * + * 1. InetAddress.getLocalHost().getHostName() - if contains + * '.' use as FQDN + * 2. if no '.' query name service for FQDN in a thread + * Note: We query the name service for an FQDN by creating + * an InetAddress via a stringified copy of the local ip + * address; this creates an InetAddress with a null hostname. + * Asking for the hostname of this InetAddress causes a name + * service lookup. + * + * 3. if name service takes too long to return, use ip address + * 4. if name service returns but response contains no '.' + * default to ipaddress. + */ + static String attemptFQDN(InetAddress localAddr) + throws java.net.UnknownHostException + { + + String hostName = localAddr.getHostName(); + + if (hostName.indexOf('.') < 0 ) { + + String hostAddress = localAddr.getHostAddress(); + FQDN f = new FQDN(hostAddress); + + int nameServiceTimeOut = + TCPEndpoint.getInt("sun.rmi.transport.tcp.localHostNameTimeOut", + 10000); + + try { + synchronized(f) { + f.getFQDN(); + + /* wait to obtain an FQDN */ + f.wait(nameServiceTimeOut); + } + } catch (InterruptedException e) { + /* propagate the exception to the caller */ + Thread.currentThread().interrupt(); + } + hostName = f.getHost(); + + if ((hostName == null) || (hostName.equals("")) + || (hostName.indexOf('.') < 0 )) { + + hostName = hostAddress; + } + } + return hostName; + } + + /** + * Method that that will start a thread to wait to retrieve a + * fully qualified domain name from a name service. The spawned + * thread may never return but we have marked it as a daemon so the vm + * will terminate appropriately. + */ + private void getFQDN() { + + /* FQDN finder will run in RMI threadgroup. */ + Thread t = AccessController.doPrivileged( + new NewThreadAction(FQDN.this, "FQDN Finder", true)); + t.start(); + } + + private synchronized String getHost() { + return reverseLookup; + } + + /** + * thread to query a name service for the fqdn of this host. + */ + public void run() { + + String name = null; + + try { + name = InetAddress.getByName(hostAddress).getHostName(); + } catch (java.net.UnknownHostException e) { + } finally { + synchronized(this) { + reverseLookup = name; + this.notify(); + } + } + } + } +} diff --git a/src/share/classes/sun/rmi/transport/tcp/TCPTransport.java b/src/share/classes/sun/rmi/transport/tcp/TCPTransport.java new file mode 100644 index 000000000..8b70ab828 --- /dev/null +++ b/src/share/classes/sun/rmi/transport/tcp/TCPTransport.java @@ -0,0 +1,867 @@ +/* + * Copyright 1996-2005 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ +package sun.rmi.transport.tcp; + +import java.lang.ref.Reference; +import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; +import java.lang.reflect.InvocationTargetException; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.rmi.RemoteException; +import java.rmi.server.ExportException; +import java.rmi.server.LogStream; +import java.rmi.server.RMIFailureHandler; +import java.rmi.server.RMISocketFactory; +import java.rmi.server.RemoteCall; +import java.rmi.server.ServerNotActiveException; +import java.rmi.server.UID; +import java.security.AccessControlContext; +import java.security.AccessController; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.logging.Level; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import sun.rmi.runtime.Log; +import sun.rmi.runtime.NewThreadAction; +import sun.rmi.transport.Channel; +import sun.rmi.transport.Connection; +import sun.rmi.transport.DGCAckHandler; +import sun.rmi.transport.Endpoint; +import sun.rmi.transport.StreamRemoteCall; +import sun.rmi.transport.Target; +import sun.rmi.transport.Transport; +import sun.rmi.transport.TransportConstants; +import sun.rmi.transport.proxy.HttpReceiveSocket; +import sun.security.action.GetIntegerAction; +import sun.security.action.GetLongAction; +import sun.security.action.GetPropertyAction; + +/** + * TCPTransport is the socket-based implementation of the RMI Transport + * abstraction. + * + * @author Ann Wollrath + * @author Peter Jones + */ +public class TCPTransport extends Transport { + + /* tcp package log */ + static final Log tcpLog = Log.getLog("sun.rmi.transport.tcp", "tcp", + LogStream.parseLevel(AccessController.doPrivileged( + new GetPropertyAction("sun.rmi.transport.tcp.logLevel")))); + + /** maximum number of connection handler threads */ + private static final int maxConnectionThreads = // default no limit + AccessController.doPrivileged( + new GetIntegerAction("sun.rmi.transport.tcp.maxConnectionThreads", + Integer.MAX_VALUE)); + + /** keep alive time for idle connection handler threads */ + private static final long threadKeepAliveTime = // default 1 minute + AccessController.doPrivileged( + new GetLongAction("sun.rmi.transport.tcp.threadKeepAliveTime", + 60000)); + + /** thread pool for connection handlers */ + private static final ExecutorService connectionThreadPool = + new ThreadPoolExecutor(0, maxConnectionThreads, + threadKeepAliveTime, TimeUnit.MILLISECONDS, + new SynchronousQueue<Runnable>(), + new ThreadFactory() { + public Thread newThread(Runnable runnable) { + return AccessController.doPrivileged(new NewThreadAction( + runnable, "TCP Connection(idle)", true, true)); + } + }); + + /** total connections handled */ + private static final AtomicInteger connectionCount = new AtomicInteger(0); + + /** client host for the current thread's connection */ + private static final ThreadLocal<ConnectionHandler> + threadConnectionHandler = new ThreadLocal<ConnectionHandler>(); + + /** endpoints for this transport */ + private final LinkedList<TCPEndpoint> epList; + /** number of objects exported on this transport */ + private int exportCount = 0; + /** server socket for this transport */ + private ServerSocket server = null; + /** table mapping endpoints to channels */ + private final Map<TCPEndpoint,Reference<TCPChannel>> channelTable = + new WeakHashMap<TCPEndpoint,Reference<TCPChannel>>(); + + static final RMISocketFactory defaultSocketFactory = + RMISocketFactory.getDefaultSocketFactory(); + + /** number of milliseconds in accepted-connection timeout. + * Warning: this should be greater than 15 seconds (the client-side + * timeout), and defaults to 2 hours. + * The maximum representable value is slightly more than 24 days + * and 20 hours. + */ + private static final int connectionReadTimeout = // default 2 hours + AccessController.doPrivileged( + new GetIntegerAction("sun.rmi.transport.tcp.readTimeout", + 2 * 3600 * 1000)); + + /** + * Constructs a TCPTransport. + */ + TCPTransport(LinkedList<TCPEndpoint> epList) { + // assert ((epList.size() != null) && (epList.size() >= 1)) + this.epList = epList; + if (tcpLog.isLoggable(Log.BRIEF)) { + tcpLog.log(Log.BRIEF, "Version = " + + TransportConstants.Version + ", ep = " + getEndpoint()); + } + } + + /** + * Closes all cached connections in every channel subordinated to this + * transport. Currently, this only closes outgoing connections. + */ + public void shedConnectionCaches() { + List<TCPChannel> channels; + synchronized (channelTable) { + channels = new ArrayList<TCPChannel>(channelTable.values().size()); + for (Reference<TCPChannel> ref : channelTable.values()) { + TCPChannel ch = ref.get(); + if (ch != null) { + channels.add(ch); + } + } + } + for (TCPChannel channel : channels) { + channel.shedCache(); + } + } + + /** + * Returns a <I>Channel</I> that generates connections to the + * endpoint <I>ep</I>. A Channel is an object that creates and + * manages connections of a particular type to some particular + * address space. + * @param ep the endpoint to which connections will be generated. + * @return the channel or null if the transport cannot + * generate connections to this endpoint + */ + public TCPChannel getChannel(Endpoint ep) { + TCPChannel ch = null; + if (ep instanceof TCPEndpoint) { + synchronized (channelTable) { + Reference<TCPChannel> ref = channelTable.get(ep); + if (ref != null) { + ch = ref.get(); + } + if (ch == null) { + TCPEndpoint tcpEndpoint = (TCPEndpoint) ep; + ch = new TCPChannel(this, tcpEndpoint); + channelTable.put(tcpEndpoint, + new WeakReference<TCPChannel>(ch)); + } + } + } + return ch; + } + + /** + * Removes the <I>Channel</I> that generates connections to the + * endpoint <I>ep</I>. + */ + public void free(Endpoint ep) { + if (ep instanceof TCPEndpoint) { + synchronized (channelTable) { + Reference<TCPChannel> ref = channelTable.remove(ep); + if (ref != null) { + TCPChannel channel = ref.get(); + if (channel != null) { + channel.shedCache(); + } + } + } + } + } + + /** + * Export the object so that it can accept incoming calls. + */ + public void exportObject(Target target) throws RemoteException { + /* + * Ensure that a server socket is listening, and count this + * export while synchronized to prevent the server socket from + * being closed due to concurrent unexports. + */ + synchronized (this) { + listen(); + exportCount++; + } + + /* + * Try to add the Target to the exported object table; keep + * counting this export (to keep server socket open) only if + * that succeeds. + */ + boolean ok = false; + try { + super.exportObject(target); + ok = true; + } finally { + if (!ok) { + synchronized (this) { + decrementExportCount(); + } + } + } + } + + protected synchronized void targetUnexported() { + decrementExportCount(); + } + + /** + * Decrements the count of exported objects, closing the current + * server socket if the count reaches zero. + **/ + private void decrementExportCount() { + assert Thread.holdsLock(this); + exportCount--; + if (exportCount == 0 && getEndpoint().getListenPort() != 0) { + ServerSocket ss = server; + server = null; + try { + ss.close(); + } catch (IOException e) { + } + } + } + + /** + * Verify that the current access control context has permission to + * accept the connection being dispatched by the current thread. + */ + protected void checkAcceptPermission(AccessControlContext acc) { + SecurityManager sm = System.getSecurityManager(); + if (sm == null) { + return; + } + ConnectionHandler h = threadConnectionHandler.get(); + if (h == null) { + throw new Error( + "checkAcceptPermission not in ConnectionHandler thread"); + } + h.checkAcceptPermission(sm, acc); + } + + private TCPEndpoint getEndpoint() { + synchronized (epList) { + return epList.getLast(); + } + } + + /** + * Listen on transport's endpoint. + */ + private void listen() throws RemoteException { + assert Thread.holdsLock(this); + TCPEndpoint ep = getEndpoint(); + int port = ep.getPort(); + + if (server == null) { + if (tcpLog.isLoggable(Log.BRIEF)) { + tcpLog.log(Log.BRIEF, + "(port " + port + ") create server socket"); + } + + try { + server = ep.newServerSocket(); + /* + * Don't retry ServerSocket if creation fails since + * "port in use" will cause export to hang if an + * RMIFailureHandler is not installed. + */ + Thread t = AccessController.doPrivileged( + new NewThreadAction(new AcceptLoop(server), + "TCP Accept-" + port, true)); + t.start(); + } catch (java.net.BindException e) { + throw new ExportException("Port already in use: " + port, e); + } catch (IOException e) { + throw new ExportException("Listen failed on port: " + port, e); + } + + } else { + // otherwise verify security access to existing server socket + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkListen(port); + } + } + } + + /** + * Worker for accepting connections from a server socket. + **/ + private class AcceptLoop implements Runnable { + + private final ServerSocket serverSocket; + + // state for throttling loop on exceptions (local to accept thread) + private long lastExceptionTime = 0L; + private int recentExceptionCount; + + AcceptLoop(ServerSocket serverSocket) { + this.serverSocket = serverSocket; + } + + public void run() { + try { + executeAcceptLoop(); + } finally { + try { + /* + * Only one accept loop is started per server + * socket, so after no more connections will be + * accepted, ensure that the server socket is no + * longer listening. + */ + serverSocket.close(); + } catch (IOException e) { + } + } + } + + /** + * Accepts connections from the server socket and executes + * handlers for them in the thread pool. + **/ + private void executeAcceptLoop() { + if (tcpLog.isLoggable(Log.BRIEF)) { + tcpLog.log(Log.BRIEF, "listening on port " + + getEndpoint().getPort()); + } + + while (true) { + Socket socket = null; + try { + socket = serverSocket.accept(); + + /* + * Find client host name (or "0.0.0.0" if unknown) + */ + InetAddress clientAddr = socket.getInetAddress(); + String clientHost = (clientAddr != null + ? clientAddr.getHostAddress() + : "0.0.0.0"); + + /* + * Execute connection handler in the thread pool, + * which uses non-system threads. + */ + try { + connectionThreadPool.execute( + new ConnectionHandler(socket, clientHost)); + } catch (RejectedExecutionException e) { + closeSocket(socket); + tcpLog.log(Log.BRIEF, + "rejected connection from " + clientHost); + } + + } catch (Throwable t) { + try { + /* + * If the server socket has been closed, such + * as because there are no more exported + * objects, then we expect accept to throw an + * exception, so just terminate normally. + */ + if (serverSocket.isClosed()) { + break; + } + + try { + if (tcpLog.isLoggable(Level.WARNING)) { + tcpLog.log(Level.WARNING, + "accept loop for " + serverSocket + + " throws", t); + } + } catch (Throwable tt) { + } + } finally { + /* + * Always close the accepted socket (if any) + * if an exception occurs, but only after + * logging an unexpected exception. + */ + if (socket != null) { + closeSocket(socket); + } + } + + /* + * In case we're running out of file descriptors, + * release resources held in caches. + */ + if (!(t instanceof SecurityException)) { + try { + TCPEndpoint.shedConnectionCaches(); + } catch (Throwable tt) { + } + } + + /* + * A NoClassDefFoundError can occur if no file + * descriptors are available, in which case this + * loop should not terminate. + */ + if (t instanceof Exception || + t instanceof OutOfMemoryError || + t instanceof NoClassDefFoundError) + { + if (!continueAfterAcceptFailure(t)) { + return; + } + // continue loop + } else { + throw (Error) t; + } + } + } + } + + /** + * Returns true if the accept loop should continue after the + * specified exception has been caught, or false if the accept + * loop should terminate (closing the server socket). If + * there is an RMIFailureHandler, this method returns the + * result of passing the specified exception to it; otherwise, + * this method always returns true, after sleeping to throttle + * the accept loop if necessary. + **/ + private boolean continueAfterAcceptFailure(Throwable t) { + RMIFailureHandler fh = RMISocketFactory.getFailureHandler(); + if (fh != null) { + return fh.failure(t instanceof Exception ? (Exception) t : + new InvocationTargetException(t)); + } else { + throttleLoopOnException(); + return true; + } + } + + /** + * Throttles the accept loop after an exception has been + * caught: if a burst of 10 exceptions in 5 seconds occurs, + * then wait for 10 seconds to curb busy CPU usage. + **/ + private void throttleLoopOnException() { + long now = System.currentTimeMillis(); + if (lastExceptionTime == 0L || (now - lastExceptionTime) > 5000) { + // last exception was long ago (or this is the first) + lastExceptionTime = now; + recentExceptionCount = 0; + } else { + // exception burst window was started recently + if (++recentExceptionCount >= 10) { + try { + Thread.sleep(10000); + } catch (InterruptedException ignore) { + } + } + } + } + } + + /** close socket and eat exception */ + private static void closeSocket(Socket sock) { + try { + sock.close(); + } catch (IOException ex) { + // eat exception + } + } + + /** + * handleMessages decodes transport operations and handles messages + * appropriately. If an exception occurs during message handling, + * the socket is closed. + */ + void handleMessages(Connection conn, boolean persistent) { + int port = getEndpoint().getPort(); + + try { + DataInputStream in = new DataInputStream(conn.getInputStream()); + do { + int op = in.read(); // transport op + if (op == -1) { + if (tcpLog.isLoggable(Log.BRIEF)) { + tcpLog.log(Log.BRIEF, "(port " + + port + ") connection closed"); + } + break; + } + + if (tcpLog.isLoggable(Log.BRIEF)) { + tcpLog.log(Log.BRIEF, "(port " + port + + ") op = " + op); + } + + switch (op) { + case TransportConstants.Call: + // service incoming RMI call + RemoteCall call = new StreamRemoteCall(conn); + if (serviceCall(call) == false) + return; + break; + + case TransportConstants.Ping: + // send ack for ping + DataOutputStream out = + new DataOutputStream(conn.getOutputStream()); + out.writeByte(TransportConstants.PingAck); + conn.releaseOutputStream(); + break; + + case TransportConstants.DGCAck: + DGCAckHandler.received(UID.read(in)); + break; + + default: + throw new IOException("unknown transport op " + op); + } + } while (persistent); + + } catch (IOException e) { + // exception during processing causes connection to close (below) + if (tcpLog.isLoggable(Log.BRIEF)) { + tcpLog.log(Log.BRIEF, "(port " + port + + ") exception: ", e); + } + } finally { + try { + conn.close(); + } catch (IOException ex) { + // eat exception + } + } + } + + /** + * Returns the client host for the current thread's connection. Throws + * ServerNotActiveException if no connection is active for this thread. + */ + public static String getClientHost() throws ServerNotActiveException { + ConnectionHandler h = threadConnectionHandler.get(); + if (h != null) { + return h.getClientHost(); + } else { + throw new ServerNotActiveException("not in a remote call"); + } + } + + /** + * Services messages on accepted connection + */ + private class ConnectionHandler implements Runnable { + + /** int value of "POST" in ASCII (Java's specified data formats + * make this once-reviled tactic again socially acceptable) */ + private static final int POST = 0x504f5354; + + /** most recently accept-authorized AccessControlContext */ + private AccessControlContext okContext; + /** cache of accept-authorized AccessControlContexts */ + private Map<AccessControlContext, + Reference<AccessControlContext>> authCache; + /** security manager which authorized contexts in authCache */ + private SecurityManager cacheSecurityManager = null; + + private Socket socket; + private String remoteHost; + + ConnectionHandler(Socket socket, String remoteHost) { + this.socket = socket; + this.remoteHost = remoteHost; + } + + String getClientHost() { + return remoteHost; + } + + /** + * Verify that the given AccessControlContext has permission to + * accept this connection. + */ + void checkAcceptPermission(SecurityManager sm, + AccessControlContext acc) + { + /* + * Note: no need to synchronize on cache-related fields, since this + * method only gets called from the ConnectionHandler's thread. + */ + if (sm != cacheSecurityManager) { + okContext = null; + authCache = new WeakHashMap<AccessControlContext, + Reference<AccessControlContext>>(); + cacheSecurityManager = sm; + } + if (acc.equals(okContext) || authCache.containsKey(acc)) { + return; + } + InetAddress addr = socket.getInetAddress(); + String host = (addr != null) ? addr.getHostAddress() : "*"; + + sm.checkAccept(host, socket.getPort()); + + authCache.put(acc, new SoftReference<AccessControlContext>(acc)); + okContext = acc; + } + + public void run() { + Thread t = Thread.currentThread(); + String name = t.getName(); + try { + t.setName("RMI TCP Connection(" + + connectionCount.incrementAndGet() + + ")-" + remoteHost); + run0(); + } finally { + t.setName(name); + } + } + + private void run0() { + TCPEndpoint endpoint = getEndpoint(); + int port = endpoint.getPort(); + + threadConnectionHandler.set(this); + + // set socket to disable Nagle's algorithm (always send + // immediately) + // TBD: should this be left up to socket factory instead? + try { + socket.setTcpNoDelay(true); + } catch (Exception e) { + // if we fail to set this, ignore and proceed anyway + } + // set socket to timeout after excessive idle time + try { + if (connectionReadTimeout > 0) + socket.setSoTimeout(connectionReadTimeout); + } catch (Exception e) { + // too bad, continue anyway + } + + try { + InputStream sockIn = socket.getInputStream(); + InputStream bufIn = sockIn.markSupported() + ? sockIn + : new BufferedInputStream(sockIn); + + // Read magic (or HTTP wrapper) + bufIn.mark(4); + DataInputStream in = new DataInputStream(bufIn); + int magic = in.readInt(); + + if (magic == POST) { + tcpLog.log(Log.BRIEF, "decoding HTTP-wrapped call"); + + // It's really a HTTP-wrapped request. Repackage + // the socket in a HttpReceiveSocket, reinitialize + // sockIn and in, and reread magic. + bufIn.reset(); // unread "POST" + + try { + socket = new HttpReceiveSocket(socket, bufIn, null); + remoteHost = "0.0.0.0"; + sockIn = socket.getInputStream(); + bufIn = new BufferedInputStream(sockIn); + in = new DataInputStream(bufIn); + magic = in.readInt(); + + } catch (IOException e) { + throw new RemoteException("Error HTTP-unwrapping call", + e); + } + } + // bufIn's mark will invalidate itself when it overflows + // so it doesn't have to be turned off + + // read and verify transport header + short version = in.readShort(); + if (magic != TransportConstants.Magic || + version != TransportConstants.Version) { + // protocol mismatch detected... + // just close socket: this would recurse if we marshal an + // exception to the client and the protocol at other end + // doesn't match. + closeSocket(socket); + return; + } + + OutputStream sockOut = socket.getOutputStream(); + BufferedOutputStream bufOut = + new BufferedOutputStream(sockOut); + DataOutputStream out = new DataOutputStream(bufOut); + + int remotePort = socket.getPort(); + + if (tcpLog.isLoggable(Log.BRIEF)) { + tcpLog.log(Log.BRIEF, "accepted socket from [" + + remoteHost + ":" + remotePort + "]"); + } + + TCPEndpoint ep; + TCPChannel ch; + TCPConnection conn; + + // send ack (or nack) for protocol + byte protocol = in.readByte(); + switch (protocol) { + case TransportConstants.SingleOpProtocol: + // no ack for protocol + + // create dummy channel for receiving messages + ep = new TCPEndpoint(remoteHost, socket.getLocalPort(), + endpoint.getClientSocketFactory(), + endpoint.getServerSocketFactory()); + ch = new TCPChannel(TCPTransport.this, ep); + conn = new TCPConnection(ch, socket, bufIn, bufOut); + + // read input messages + handleMessages(conn, false); + break; + + case TransportConstants.StreamProtocol: + // send ack + out.writeByte(TransportConstants.ProtocolAck); + + // suggest endpoint (in case client doesn't know host name) + if (tcpLog.isLoggable(Log.VERBOSE)) { + tcpLog.log(Log.VERBOSE, "(port " + port + + ") " + "suggesting " + remoteHost + ":" + + remotePort); + } + + out.writeUTF(remoteHost); + out.writeInt(remotePort); + out.flush(); + + // read and discard (possibly bogus) endpoint + // REMIND: would be faster to read 2 bytes then skip N+4 + String clientHost = in.readUTF(); + int clientPort = in.readInt(); + if (tcpLog.isLoggable(Log.VERBOSE)) { + tcpLog.log(Log.VERBOSE, "(port " + port + + ") client using " + clientHost + ":" + clientPort); + } + + // create dummy channel for receiving messages + // (why not use clientHost and clientPort?) + ep = new TCPEndpoint(remoteHost, socket.getLocalPort(), + endpoint.getClientSocketFactory(), + endpoint.getServerSocketFactory()); + ch = new TCPChannel(TCPTransport.this, ep); + conn = new TCPConnection(ch, socket, bufIn, bufOut); + + // read input messages + handleMessages(conn, true); + break; + + case TransportConstants.MultiplexProtocol: + if (tcpLog.isLoggable(Log.VERBOSE)) { + tcpLog.log(Log.VERBOSE, "(port " + port + + ") accepting multiplex protocol"); + } + + // send ack + out.writeByte(TransportConstants.ProtocolAck); + + // suggest endpoint (in case client doesn't already have one) + if (tcpLog.isLoggable(Log.VERBOSE)) { + tcpLog.log(Log.VERBOSE, "(port " + port + + ") suggesting " + remoteHost + ":" + remotePort); + } + + out.writeUTF(remoteHost); + out.writeInt(remotePort); + out.flush(); + + // read endpoint client has decided to use + ep = new TCPEndpoint(in.readUTF(), in.readInt(), + endpoint.getClientSocketFactory(), + endpoint.getServerSocketFactory()); + if (tcpLog.isLoggable(Log.VERBOSE)) { + tcpLog.log(Log.VERBOSE, "(port " + + port + ") client using " + + ep.getHost() + ":" + ep.getPort()); + } + + ConnectionMultiplexer multiplexer; + synchronized (channelTable) { + // create or find channel for this endpoint + ch = getChannel(ep); + multiplexer = + new ConnectionMultiplexer(ch, bufIn, sockOut, + false); + ch.useMultiplexer(multiplexer); + } + multiplexer.run(); + break; + + default: + // protocol not understood, send nack and close socket + out.writeByte(TransportConstants.ProtocolNack); + out.flush(); + break; + } + + } catch (IOException e) { + // socket in unknown state: destroy socket + tcpLog.log(Log.BRIEF, "terminated with exception:", e); + } finally { + closeSocket(socket); + } + } + } +} |