Added: incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/Main.java URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/Main.java?view=auto&rev=158176 ============================================================================== --- incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/Main.java (added) +++ incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/Main.java Fri Mar 18 17:02:29 2005 @@ -0,0 +1,472 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Main.java + * + * Created on June 4, 2001, 9:59 AM + */ + +package org.apache.jdo.impl.fostore; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.EOFException; +import java.io.InputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.Date; +import java.util.HashSet; +import java.util.Iterator; + +import javax.jdo.JDOFatalInternalException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.jdo.util.I18NHelper; + +/** + * Standalone server for FOStore databases. + * <p> + * This class is <code>public</code> because it has a <code>main</code> entry + * point for running as a standalone program. + * + * @author Craig Russell + * @version 1.0 + */ +public class Main { + + /** I18N support. */ + private static final I18NHelper msg = I18NHelper.getInstance(I18N.NAME); + + /** Logger */ + static final Log logger = LogFactory.getFactory().getInstance( + "org.apache.jdo.impl.fostore"); // NOI18N + + /** The port number to use for incoming connections. + */ + int port; + + /** The backlog for Socket.listen. + */ + int backlog; + + /** Default backlog. + */ + private static final int DEFAULT_BACKLOG = 5; + + /** The number of seconds with no activity before shutting down. + */ + int timeout; + + /** Default timeout. + */ + private static final int DEFAULT_TIMEOUT = 60; + + /** The root of the file system for database path names. + * Currently this property is ignored. + */ + String root = null; + + /** The time this server started running. + */ + final Date startTime = new Date(); + + /** A flag indicating that the server is shutting down. + */ + static boolean shutdown = false; + + /** The synchronizing Object for the shutdown flag. + */ + static Object shutdownSynchronizer = new Object(); + + /** The Thread responsible for detecting inactivity. + */ + static Thread timeoutThread = null; + + /** The Thread responsible for listening for incoming connection requests. + */ + Thread listenerThread = null; + + /** The set of Threads servicing incoming connections. + */ + HashSet serviceThreads = new HashSet (); + + /** Creates new Main */ + public Main () { + } + + /** The main routine. + * @param args the command line arguments + */ + public static void main (String args[]) { + if (args.length == 0) { + usage(); + } + Main main = new Main(); + main.run (args); + } + + /** Print the usage message on standard output. + */ + static void usage () { + // Turn int into a String to avoid having the formatter localize it by + // (e.g., with EN_US) putting in a comma (i.e., print 9919, not 9,919). + print(msg.msg("MSG_MainUsage1", // NOI18N + new Integer(FOStoreRemoteConnection.DEFAULT_PORT).toString())); + print(msg.msg("MSG_MainUsage2", DEFAULT_BACKLOG)); // NOI18N + print(msg.msg("MSG_MainUsage3", DEFAULT_TIMEOUT)); // NOI18N + } + + /** Run the main program. + * @param args the command line arguments + */ + void run (String args[]) { + boolean debug = logger.isDebugEnabled(); + if (debug) { + logger.debug("FOStore Main started: " + startTime); // NOI18N + } + timeout = Integer.getInteger( + "timeout", DEFAULT_TIMEOUT).intValue(); // NOI18N + port = Integer.getInteger( + "port", FOStoreRemoteConnection.DEFAULT_PORT).intValue(); // NOI18N + backlog = Integer.getInteger( + "backlog", DEFAULT_BACKLOG).intValue(); // NOI18N + root = System.getProperty("root"); // NOI18N + if ((root == null) || root.equals("")) { // NOI18N + root = System.getProperty("user.dir"); // NOI18N + } + + if (debug) { + logger.debug("\ttimeout = " + timeout); // NOI18N + logger.debug("\tport = " + port); // NOI18N + logger.debug("\tbacklog = " + backlog); // NOI18N + logger.debug("\troot = " + root); // NOI18N + } + + startTimeoutThread(); + startListenerThread(); + try { + timeoutThread.join(); + setShutdown(); + listenerThread.interrupt(); + listenerThread.join(); + } catch (InterruptedException ie) { + // do nothing + if (debug) { + logger.debug("Main: timeoutThread.join() caught InterruptedException."); // NOI18N + } + } finally { + if (debug) { + logger.debug("Main: FOStore timeout thread ended: " + + new Date().toString()); // NOI18N + } + setShutdown(); + for (Iterator serviceThreadIterator = serviceThreads.iterator(); + serviceThreadIterator.hasNext();) { + try { + Thread serviceThread = (Thread) serviceThreadIterator.next(); + serviceThread.join(); + } catch (InterruptedException ie) { + if (debug) { + logger.debug("Main: serviceThread.join() caught InterruptedException."); // NOI18N + } + } + } + } + if (debug) { + logger.debug("Main: FOStore shutdown."); // NOI18N + } + } + + /** Start the TimeoutThread. + */ + void startTimeoutThread() { + Runnable timeoutRunnable = new TimeoutRunnable (timeout); + timeoutThread = new Thread (timeoutRunnable, "TimeoutThread"); // NOI18N + timeoutThread.start(); + if (logger.isDebugEnabled()) { + logger.debug("Main: TimeoutThread started."); // NOI18N + } + } + + static void resetTimeout() { + timeoutThread.interrupt(); + } + + /** The Timeout Runnable class. This class watches a timer, + * and whent the timer expires, the thread terminates. + * This causes the Main thread to fall through its join on + * the timeout thread and completes the shutdown process. + */ + class TimeoutRunnable implements Runnable { + /** The number of milliseconds to sleep before terminating this thread. + * Another thread wishing to reset the timeout will + * interrupt this thread. + */ + int timeoutMillis = timeout * 1000; + /** Construct an instance of the TimeoutRunnable with the specified + * number of seconds to sleep before terminating. + * @param timeout the number of seconds before timeout. + */ + TimeoutRunnable (int timeout) { + timeoutMillis = timeout * 1000; + } + + /** Run the timeout thread. + */ + public void run() { + boolean debug = logger.isDebugEnabled(); + + boolean awake = false; + if (debug) { + logger.debug("TimeoutThread using: " + + timeoutMillis + " milliseconds"); // NOI18N + } + while (!awake) { + try { + Thread.sleep (timeoutMillis); + awake = true; + } catch (InterruptedException ie) { + if (debug) { + logger.debug("TimeoutThread caught InterruptedException; continuing to sleep"); // NOI18N + } + } + } + if (debug) { + logger.debug("TimeoutThread ending."); // NOI18N + } + } + } + + /** Start the Listener Thread. + */ + void startListenerThread() { + Runnable listenerRunnable = new ListenerRunnable (port); + listenerThread = new Thread (listenerRunnable, "ListenerThread"); // NOI18N + listenerThread.start(); + if (logger.isDebugEnabled()) { + logger.debug("Main: ListenerThread started."); // NOI18N + } + } + + /** The Listener Thread class. This class creates an + * incoming Socket and listens on it. When a connection + * comes in, create a service thread using the new Socket + * and run it. + */ + class ListenerRunnable implements Runnable { + /** The port number to listen on. + */ + int port; + /** The Runnable class for the Listener Thread. + * @param port the port number to listen on. + */ + ListenerRunnable (int port) { + this.port = port; + } + /** Run the listener thread. Create a ServerSocket using the port + * and backlog parameters and listen on it. For each incoming + * request, create a ConnectionRunnable and start a thread to + * service the request. + * This thread continues to accept incoming connections until the + * shutdown flag is set, at which point it terminates. + */ + public void run() { + boolean debug = logger.isDebugEnabled(); + try { + if (debug) { + logger.debug("ListenerThread using port: " + port); // NOI18N + } + ServerSocket listener = new ServerSocket (port, backlog); + if (debug) { + logger.debug("ListenerThread using ServerSocket: " + + listener); // NOI18N + } + while (true) { + if (getShutdown()) break; + if (debug) { + logger.debug("ListenerThread accepting new connections."); // NOI18N + } + final Socket connection = listener.accept(); + if (debug) { + logger.debug("ListenerThread accepted " + connection); // NOI18N + } + if (connection.getLocalPort() == 0 & + connection.getPort() == 0) { + // must be a bogus shutdown connection + if (debug) { + logger.debug("Bugus connection ignored: " + connection); // NOI18N + } + continue; + } + Runnable connectionRunnable = + new ConnectionRunnable (connection); + Thread connectionThread = + new Thread (connectionRunnable, "Connection"); // NOI18N + serviceThreads.add(connectionThread); + connectionThread.start(); + } + } catch (java.net.UnknownHostException uhe) { + if (debug) { + logger.debug("ListenerThread caught UnknownHostException"); // NOI18N + } + } catch (java.net.BindException ioe) { + if (debug) { + logger.debug("ListenerThread caught BindException"); // NOI18N + } + ioe.printStackTrace(); + } catch (java.io.IOException ioe) { + if (debug) { + logger.debug("ListenerThread caught IOException"); // NOI18N + } + ioe.printStackTrace(); + } finally { + if (debug) { + logger.debug("ListenerThread ending."); // NOI18N + } + } + } + } + + /** The Runnable class for incoming connections. + */ + class ConnectionRunnable implements Runnable { + /** The Socket that received an incoming request. + */ + Socket socket; + /** The Runnable class for incoming connections. + * @param conn the socket which received a connection request. + */ + ConnectionRunnable (Socket conn) { + socket = conn; + } + /** Run the Connection Thread. This handles the incoming + * connection in the handleConnection method. + */ + public void run () { + if (logger.isDebugEnabled()) { + logger.debug("ConnectionRunnable started."); // NOI18N + } + handleConnection (socket); + serviceThreads.remove(this); + if (logger.isDebugEnabled()) { + logger.debug("ConnectionRunnable ending."); // NOI18N + } + } + } + + /** Handle the incoming connection. This method should create a new + * handler instance to read the messages from the connection, parse + * the message, determine which database is being used, and handle + * the requests. + * @param socket the socket connected by the listener + */ + void handleConnection (Socket socket) { + boolean info = logger.isInfoEnabled(); + FOStoreServerConnectionImpl server = + new FOStoreServerConnectionImpl(socket, root); + boolean connected = true; + while (connected) { + + if (info) { + logger.info("Main.handleConnection"); // NOI18N + } + + try { + resetTimeout(); // reset the timeout thread on each message received + server.readInputFromClient(); + } catch (EOFException ioe) { + connected = false; // normal case of EOF indicating remote side closed + break; + } catch (IOException ioe) { + connected = false; + throw new JDOFatalInternalException ( + msg.msg("ERR_HandleConnectionReadIOException"), ioe); // NOI18N + } + + if (info) { + logger.info("Main.handleConnection: processRequests"); // NOI18N + } + server.processRequests(); + + try { + if (info) { + logger.info("Main.handleConnection: release & write"); // NOI18N + } + server.releaseDatabase(); + server.writeOutputToClient(); + } catch (IOException ioe) { + connected = false; + ioe.printStackTrace(); // should not occur + throw new JDOFatalInternalException ( + msg.msg("ERR_HandleConnectionWriteIOException"), ioe); // NOI18N + } catch (InterruptedException ioe) { + connected = false; + ioe.printStackTrace(); // should not occur + throw new JDOFatalInternalException ( + msg.msg("ERR_HandleConnectionWriteInterruptedException"), + ioe); // NOI18N + } + } + try { + if (info) { + logger.info("Main.handleConnection: close server, socket"); // NOI18N + } + server.close(); + socket.close(); + } catch (Exception e) { + if (logger.isDebugEnabled()) { + e.printStackTrace(); + } + } + } + + /** Test if the server is shutting down. + * @return if the server is shutting down. + */ + static boolean getShutdown() { + synchronized (shutdownSynchronizer) { + return shutdown; + } + } + + /** Set the shutdown flag. + */ + static void setShutdown() { + synchronized (shutdownSynchronizer) { + shutdown = true; + } + } + + /** Print a message on the standard output. + * @param s the message to print. + */ + static void print (String s) { + System.out.println (s); + } + + /** Flush the standard output. + */ + static void flush() { + System.out.flush(); + } + +}
Added: incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/Message.java URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/Message.java?view=auto&rev=158176 ============================================================================== --- incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/Message.java (added) +++ incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/Message.java Fri Mar 18 17:02:29 2005 @@ -0,0 +1,321 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jdo.impl.fostore; + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map.Entry; +import java.util.Map; + +import javax.jdo.JDOFatalException; +import javax.jdo.JDOFatalInternalException; +import javax.jdo.JDOFatalDataStoreException; +import javax.jdo.JDOFatalUserException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.jdo.store.Connector; +import org.apache.jdo.util.I18NHelper; + +/** +* Represents a set of one or more requests that will be sent to the store. +* @see Request +* +* @author Dave Bristor +*/ +class Message { + /** Connector for which this message acts as a transport. */ + private final Connector connector; + + /** Set of request objects that are in the process of carrying out their + * function; maps from a RequestId to Request. When a request is + * created, it adds itself to this map. When a request's reply is + * received, it is looked up by RequestId in this map, removed, and the + * Request object given the reply. + */ + private final HashMap requests = new HashMap(); + + /** Set of CLIDs associated with ActivateClass requests that are in this + * Message. It is cleared by initOutput. */ + private final HashSet clids = new HashSet(); + + /** + * This contains the Message's actual data for the store. + */ + private final FOStoreOutput out = new FOStoreOutput(); + + /** + * Indicates the number of requests that have been written into this + * Message. + */ + private int numRequests = 0; + + /** + * Contains the position in the output of the numRequest stashed by + * initOutput. This is modified and used only by initOutput and + * finishOutput. + */ + private int numRequestStash = 0; + + /** + * The version number of the current protocol. In future, this version + * number can be used to identify mismatches in protocol. + * The format is (short)major; (byte)minor; (byte)patch + * Only use major for compatibility checks; always bump major when + * incompatibly changing protocol. + */ + private static final int VERSION_NUMBER = 0x00010000; // version 1.0.0 + + /** I18N support. */ + private static final I18NHelper msg = I18NHelper.getInstance(I18N.NAME); + + /** Logger */ + static final Log logger = LogFactory.getFactory().getInstance( + "org.apache.jdo.impl.fostore"); // NOI18N + + /** Constructor for Message. The output stream is initialized to + * contain the version number and a stash for the number of requests. + * @param connector The Connector on whose behalf this Message is serving + * as a transport. May be null. + */ + Message(Connector connector) { + this.connector = connector; + initOutput(); + } + + Message() { + this(null); + } + + /** + * @return The connector associated with this Message. + */ + Connector getConnector() { + return connector; + } + + /** Return the FOStoreOutput stream for requests to be inserted. + * @return the FOStoreOutput under construction + */ + public FOStoreOutput getOutput() { + return out; + } + + /** + * Write this message to the given connection, and read replies from that + * connection, processing replies as they are read. + * @see RequestHandler#handleRequests for stream header reader. + * @param con the FOStoreClientConnection for this message + */ + void processInStore(FOStoreClientConnection con, + boolean okToReleaseConnection) { + + try { + sendToStore (con); + } finally { + try { + receiveFromStore (con, okToReleaseConnection); + } finally { + initOutput(); // Prepare for next send. + } + } + } + + /** Send the current Message buffer to the store. The data contained + * in out is written as one block of data. The connection's + * sendToStore is responsible for sending the data and handling the + * processing at the server side. + * @param con the FOStoreClientConnection + */ + private void sendToStore(FOStoreClientConnection con) { + try { + finishOutput(); + if (logger.isTraceEnabled()) { + Tester.dump("MsTS", out.getBuf(), out.getPos()); // NOI18N + } + con.sendToStore(out.getBuf(), 0, out.getPos()); + } catch (IOException ex) { + throw new JDOFatalDataStoreException( + msg.msg("ERR_SendToStore"), ex); // NOI18N + } + } + + /** Receive the replies from the store and process them. + * @param con the FOStoreConnection with the replies. + */ + private void receiveFromStore(FOStoreClientConnection con, + boolean okToReleaseConnection) { + + try { + DataInput di = con.getInputFromServer(); + + // Process the replies *and then* close the connection, to prevent + // the byte array underlying data input (which is property of the + // connection) from being overwritten by another thread. + ReplyHandler.processReplies(di, this); + } catch (IOException ioe) { + throw new JDOFatalDataStoreException ( + msg.msg("ERR_ReceiveFromStore"), ioe); // NOI18N + } finally { + if (okToReleaseConnection) { + if (logger.isDebugEnabled()) { + logger.debug( + "Message.receiveFromStore: closing connection"); //NOI18N + } + closeConnection(con); + } + } + } + + + /** + * Maps the given request to the given requestId. + * @param requestId Identifies a request within a JVM. + * @param request A request for some operation on the store for which a + * reply is expected. */ + void putRequest(RequestId requestId, Request request) { + if (logger.isDebugEnabled()) { + logger.debug("Msg.putRequest: " + requestId + // NOI18N + " " + request.getClass().getName()); // NOI18N + } + if (requests.containsKey(requestId)) { + throw new FOStoreFatalInternalException( + this.getClass(), "putRequest", // NOI18N + msg.msg("ERR_DuplicateRequestID", requestId)); // NOI18N + } else { + numRequests++; + requests.put(requestId, request); + } + } + + /** + * Provides the Request corresponding to the give requestId, removing it + * from the internal map of requests (i.e., subsequent getRequest + * invocations for the same requestId will return null). + * @param requestId Identifier for a particular request in this JVM. + * @return The Request identified by the given identifier or null if there + * is no such Request. + */ + Request getRequest(RequestId requestId) { + if (logger.isDebugEnabled()) { + logger.debug ("Msg.getRequest: " + requestId); // NOI18N + } + Request rc = (Request)requests.remove(requestId); + if (rc == null) { // oops, this should never happen + if (logger.isDebugEnabled()) { + logger.debug ("Msg.getRequest: unable to find: " + requestId); // NOI18N + for (Iterator it = requests.entrySet().iterator();it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + logger.debug ("Msg.getRequest: found: " + // NOI18N + "entry.key: " + entry.getKey() + // NOI18N + " entry.Value: " + entry.getValue()); // NOI18N + } + } + } + return rc; + } + + /** + * Returns true if this message has requests for the store. + * @return true if there are any messages. + */ + boolean hasRequests() { + return numRequests > 0; + } + + /** Initialize the output buffer with version number and a stash for number + * of requests. + */ + private void initOutput() { + try { + out.setPos(0); + out.writeInt (VERSION_NUMBER); + numRequests = 0; + numRequestStash = out.beginStash(); + clids.clear(); + } catch (IOException ioe) { + throw new JDOFatalInternalException ( + msg.msg("ERR_InitOutput"), ioe); // NOI18N + } + } + + /** Finish the output buffer by updating the stash with number of requests. + */ + private void finishOutput () { + try { + out.endStash (numRequests, numRequestStash); + } catch (IOException ioe) { + throw new JDOFatalInternalException ( + msg.msg("ERR_FinishOutput"), ioe); // NOI18N + } + } + + /** Close the connection to the store. + * @param con the connection to close. + */ + private void closeConnection(FOStoreClientConnection con) { + if (logger.isDebugEnabled()) { + logger.debug ("Msg.closeConnection:" + con); // NOI18N + } + try { + con.close(); + } catch (IOException ioe) { + throw new JDOFatalInternalException ( + msg.msg("ERR_CloseConnection"), ioe); // NOI18N + } + } + + /** + * Verify a Message's version number. + * @throws JDOFatalUserException if the version number does not match + * that in the caller's JVM. + */ + static void verifyVersionNumber(DataInput in) throws IOException { + int verNum = in.readInt(); + if (VERSION_NUMBER != verNum) { + throw new JDOFatalUserException( + msg.msg("EXC_MessageVersionMismatch", // NOI18N + new Integer(verNum), new Integer(VERSION_NUMBER))); + } + } + + /** + * Add the given CLID to the set of CLIDs maintained by this Message. + */ + void addCLID(CLID clid) { + clids.add(clid); + } + + /** + * Returns true if the given CLID is in this Message's set of CLIDs. + */ + boolean containsCLID(CLID clid) { + return clids.contains(clid); + } + + // Debug support + /** Dump the complete current contents of the message buffer. + */ + public void dump() { + Tester.dump("MSG", out.getBuf(), out.getPos()); // NOI18N + } +} Added: incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/OID.java URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/OID.java?view=auto&rev=158176 ============================================================================== --- incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/OID.java (added) +++ incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/OID.java Fri Mar 18 17:02:29 2005 @@ -0,0 +1,462 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jdo.impl.fostore; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +import java.util.StringTokenizer; + +import javax.jdo.JDOFatalException; +import javax.jdo.JDOUserException; +import javax.jdo.spi.PersistenceCapable; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.jdo.state.StateManagerInternal; +import org.apache.jdo.util.I18NHelper; +import org.netbeans.mdr.persistence.btreeimpl.btreestorage.BtreeFactory; +import org.netbeans.mdr.persistence.Storage; +import org.netbeans.mdr.persistence.MOFID; + +/** + * Represents the identity of a JDO object in the File/Object store. This + * implementation uses datastore identity. The identity is based on the class + * of the object and a unique identifier within that class. These OID's are + * unique only within a single datastore. + * <p> + * This class is <code>public</code> as required by the JDO specification. + * + * @author Dave Bristor + * @version 1.0.1 + */ +// XXX TBD Remote: Allocate provisional OID's per-PMF w/ remote store. +public class OID implements Serializable, Comparable { + // + // Make sure that the number of bits uses here add up to the right number + // of bits as per the size of the OID. + // + // In the initial implementation, we are using a long as the size of the + // OID. The layout is: + // [reserved: 2] [provisional-CLID: 1] [provisional-UID: 1] [class id: 12] [unique id: 48] + // + static final long RESERVED_MASK = 0xc000000000000000L; + static final long PROV_UID_MASK = 0x1000000000000000L; + static final long PROV_CLID_MASK = 0x2000000000000000L; + static final long CLID_MASK = 0x0fff000000000000L; + static final long UID_MASK = 0x0000ffffffffffffL; + + // Shift a clid's id by this much to "or" it into an oid. + static final int CLID_SHIFT = 48; + + // Maximum value for a CLID and UID. + static final int MAX_CLID = (int)(CLID_MASK >> CLID_SHIFT); + static final long MAX_UID = UID_MASK; + + // Shift the reserved bits over by this much to "or" them into an oid. + static final int RESERVED_SHIFT = 61; + + /** + * The 'value' of this OID. + */ + // JDO spec - required. + public long oid; + + // Hashcode uniquely identifying this CLID within this JVM. + private int hashCode; + + // The value for oid that will be used by the next-created provisional OID. + private static long nextProvisional = 0; + + // The Class that defined this OID + private Class pcClass = null; + + // For synchronizing access to nextProvisional; + private static final Integer lock = new Integer(1); + + /** I18N support. */ + static final I18NHelper msg = I18NHelper.getInstance(I18N.NAME); + + /** Logger */ + static final Log logger = LogFactory.getFactory().getInstance( + "org.apache.jdo.impl.fostore"); // NOI18N + + // + // OID's are created 3 ways: + // * Creating an "empty" Oid and then (presumably) setting + // the long oid value. + // * Creating a filled-in OID from a long value. + // * Creting an OID from a CLID. + // + + /** + * Creates an OID with the no value. + */ + // JDO spec - required. + public OID() { } + + /** + * Constructor that takes the result of toString() and creates a new + * OID. Currently only the CLID and UID are used. The provisional + * bits are ignored. + * @see org.apache.jdo.impl.fostore.OID#toString + */ + // JDO spec - required. + public OID (String str) { + StringTokenizer st = new StringTokenizer (str, "OID: -()"); // NOI18N + String clid = st.nextToken(); + String uid = st.nextToken(); + long clidBits = Long.parseLong (clid); + long uidBits = Long.parseLong (uid); + oid = (clidBits << CLID_SHIFT) | uidBits; + } + + /** + * Creates an OID with the given value. + */ + // public for user convenience + public OID(long oid) { + this.oid = oid; + } + + // Returns a new OID for the given CLID. The OID is provisional if the + // CLID is provisional. + private OID(CLID clid) { + synchronized(lock) { + long clidBits = clid.getId(); + clidBits <<= CLID_SHIFT; + oid = clidBits | ++nextProvisional; + + if (clid.isProvisional()) { + oid |= (PROV_UID_MASK | PROV_CLID_MASK); + } + } + } + + // + // I prefer to avoid using constructors outside of a given class, and to + // instead use factory methods. The above constructors which are public + // are that way for the sake of the JDO spec. Code within fostore uses + // these factory methods instead. + // + + /** + * Create and return a provisional OID + * @return A Provisional OID. + */ + static OID create(CLID clid) { + OID rc = new OID(clid); + if (clid.isProvisional()) { + rc.oid |= PROV_CLID_MASK; + } + rc.oid |= PROV_UID_MASK; + return rc; + } + + /** + * Create and return a based on a given representation. + * @return A real, non-provisional OID. + * @exception JDOFatalException Thrown if given oid has its provisional bit set. + */ + OID create(long oid) { + OID rc = new OID(oid); + if (rc.isProvisional()) { + throw new FOStoreFatalInternalException( + this.getClass(), "create(oid)", // NOI18N + msg.msg("ERR_InvalidAttemptToCreateOID", new Long(oid))); // NOI18N + } + return rc; + } + + /** + * Provides an OID for the given CLID and UID. The given CLID must not be + * provisional, or a JDOFatalException will result. + * @param clid CLID for the OID. + * @param uid UID part of the OID. + * @return A new OID based on the given clid and uid. + * @exception JDOFatalException Thrown if given CLID is provisional. + */ + static OID create(CLID clid, long uid) { + if (clid.isProvisional()) { + throw new FOStoreFatalInternalException( + OID.class, "create(clid, oid)", // NOI18N + msg.msg("ERR_InvalidAttemptToCreateOID", clid, new Long(uid))); // NOI18N + } + long clidBits = clid.getId(); + clidBits = clidBits << CLID_SHIFT; + return new OID(clidBits | uid); + } + + // + // Provide access to information about an OID. + // + + /** + * Indicates whether this OID is provisional. + * @return true if this OID is provisional, false otherwise. + */ + public boolean isProvisional() { + boolean rc = false; + // If CLID is provisional, it *must* be provisional. If not, it can + // still be a real CLID with a provisional UID part. + if ((oid & PROV_CLID_MASK) != 0) { + rc = true; + } else if ((oid & PROV_UID_MASK) != 0) { + rc = true; + } + return rc; + } + + /** + * Provides the CLID part of this OID. The resulting CLID is + * provisional if it is provisional within this OID. I.e., this might be a + * provisional OID, but the CLID part could still be datastore-assigned. + * @return The CLID part of this OID. + */ + public CLID getCLID() { + long clidBits = oid & CLID_MASK; + clidBits >>= CLID_SHIFT; + return CLID.create((int)clidBits, (oid & PROV_CLID_MASK) != 0); + } + + /** + * Provides the unique id part of this OID. + * @return The unique id part of this OID. + */ + public long getUID() { + return oid & UID_MASK; + } + + /** + * Provides a JVM-unique hashCode for this OID. + */ + public int hashCode() { + if (0 == hashCode) { + hashCode = new Long(oid).hashCode(); + } + return hashCode; + } + + /** + * Determines if this OID is equal to another. + * @param other The other OID in the equality comparison. + * @return True if they are equal, false otherwise. + */ + public boolean equals(Object other) { + boolean rc = false; + if (other instanceof OID) { + rc = oid == ((OID)other).oid; + } + return rc; + } + + /** + * Returns a String representation of this OID. Includes whether or not + * the OID is provisional, and its reserved bits, if they are set. + */ + public String toString() { + StringBuffer rc = + new StringBuffer( + "OID: " + // NOI18N + ((oid & CLID_MASK) >> CLID_SHIFT) + + "-" + (oid & UID_MASK)); // NOI18N + if (isProvisional()) { + rc.append(" (provisional)"); // NOI18N + } + long res = oid & RESERVED_MASK; + if (res > 0) { + res = res >> RESERVED_SHIFT; + rc.append(" (reserved=" + res + ")"); // NOI18N + } + return rc.toString(); + } + + /** + * Returns the id itself in String form, for debugging. + */ + public String oidString() { + return "" + oid; // NOI18N + } + + // + // Serialization + // We provide the {write,read}Object methods for java.io.Serialization, so + // that we know exactly what's being written and read. We also have + // methods used elsewhere in the fostore package that don't rely + // ObjectOutput stuff. + // + + /** + * Writes this OID to the output stream. + */ + private void writeObject(ObjectOutputStream out) throws IOException { + write(out); + } + + /** + * Reads this OID's value from the input stream. + */ + private void readObject(ObjectInputStream in) throws IOException { + boolean appIdType = in.readBoolean(); + oid = in.readLong(); + } + + void write(DataOutput out) throws IOException { + if (logger.isDebugEnabled()) { + logger.debug("OID.write: " + oid); // NOI18N + } + out.writeBoolean(false); + out.writeLong(oid); + } + + static OID read(DataInput in) throws IOException { + boolean appIdType = in.readBoolean(); + long oid = in.readLong(); + if (logger.isDebugEnabled()) { + logger.debug("OID.read: " + oid + " appIdType: " + appIdType); // NOI18N + } + OID rc = null; + if (appIdType) { + rc = new AID(oid); + ((AID)rc).readBuffer(in); + } else { + rc = new OID(oid); + } + return rc; + } + + /** + * Skip OID bytes from the input. + * @param in DataInput. + * @throws IOException. + */ + static void skip(DataInput in) throws IOException { + boolean appIdType = in.readBoolean(); + long oid = in.readLong(); + if (logger.isDebugEnabled()) { + logger.debug("OID.skip: " + oid + " appIdType: " + appIdType); // NOI18N + } + if (appIdType) { + int length = in.readInt(); + in.skipBytes(length); + } + } + + // + // Implementation methods. + // + + /** + * Returns copy of the requested oid to be accessed by the user. + */ + Object getExternalObjectId(PersistenceCapable pc) { + return new OID(oid); + } + + /** + * Provides the OID in a form that can be used by the database as a key. + */ + MOFID keyValue(FOStoreBtreeStorage storage) { + return storage.createMOFID(getCLID().getId(), getUID()); + } + + /** Replaces provisional oid with real oid (datastore identity only) + * @param realOID as OID instance + * @param pmf as FOStorePMF + * @param sm as StateManagerInternal + */ + void replaceProvisionalOIDWithReal(OID realOID, FOStorePMF pmf, + StateManagerInternal sm) { + + pmf.mapProvisionalOIDToReal(this, realOID); + sm.setObjectId(realOID); + } + + /** + * Returns copy of the requested oid. + */ + OID copy() { + return new OID(oid); + } + + /** + * Copy key fields from OID into PC instance. No-op for the + * datastore identity type for this OID. + * @param sm as StateManagerInternal + * @param pmf as FOStorePMF + * @param pcClass Class of the PC instance. + * @param pkfields array of PK field numbers. + */ + void copyKeyFieldsToPC(StateManagerInternal sm, FOStorePMF pmf, + Class pcClass, int[] pkfields) {} + + /** + * Returns Class that defined OID. + * @param pmf as FOStorePMF + */ + Class getPCClass(FOStorePMF pmf) { + if (pcClass == null) { + FOStoreModel model = pmf.getModel(); + pcClass = model.getClass(getCLID()); + if (logger.isDebugEnabled()) { + logger.debug("OID.getPCClass: " + getCLID() + " " + pcClass); // NOI18N + } + } + return pcClass; + } + + /** + * Returns false for application identity type for this OID. + */ + boolean isApplicationIdentity() { + return false; + } + + /** + * Returns true for datastore identity type for this OID. + */ + boolean isDataStoreIdentity() { + return true; + } + + /** Compare this OID to another OID. This is needed to implement an + * absolute ordering of OIDs. The ordering must provide for comparing + * provisional OIDs to permanent OIDs, with all provisional OIDs + * comparing greater than all permanent OIDs. + * @since 1.0.1 + */ + public int compareTo(Object obj) { + if (!(obj instanceof OID)) { + throw new JDOUserException(msg.msg("EXC_CannotCompareNonOID")); // NOI18N + } + OID other = (OID)obj; + // if other is provisional and we're not, other is bigger + if (other.isProvisional() & !this.isProvisional()) { + return -1; + } else if (this.isProvisional() & !other.isProvisional()) { + return 1; + } else // compare UIDs + // both are provisional or both not; which is bigger UID? + return (this.getUID() < other.getUID())?-1:1; + } + +}