Added: incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/ReplyHandler.java URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/ReplyHandler.java?view=auto&rev=158176 ============================================================================== --- incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/ReplyHandler.java (added) +++ incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/ReplyHandler.java Fri Mar 18 17:02:29 2005 @@ -0,0 +1,249 @@ +/* + * 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.IOException; +import java.util.ArrayList; + +import javax.jdo.JDODataStoreException; +import javax.jdo.JDOFatalDataStoreException; +import javax.jdo.JDOFatalUserException; +import javax.jdo.JDOOptimisticVerificationException; +import javax.jdo.JDOUserException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.jdo.state.StateManagerInternal; +import org.apache.jdo.store.Connector; +import org.apache.jdo.util.I18NHelper; + +/** +* Processes replies that are received from the store. Dispatches each one to +* its corresponding Request. +* @author Dave Bristor +*/ + +// Perhaps this class isn't necessary, and it's functionality should be +// moved into AbstractRequest. In any case, either it should be called +// ReplyProcessor or the single method should be named handleReplies. + +// This is client-side code. It does not need to live in the server. +abstract class ReplyHandler { + /** 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 + + /** + * Process all replies in the given input stream. The format of the + * DataInput is<br> + * <pre> + * Version number of the Reply data (of the whole enchilada, not of the + * individual Reply instances). + * Status value indicating the overall success by the server in processing + * the Message. + * </pre> + * The expected Status value is either OK or FATAL. If FATAL, then the + * next item is + * <pre> + * String: message from server (such as exception string or stack trace). + * </pre> + * Otherwise, the next item is + * <pre> + * int: number of replies + * </pre> + * In the FATAL case, all other data is ignored. Otherwise the remaining + * data is, per reply: + * <pre> + * RequestId: of the request corresponding to the reply data being read + * Status: of the individual reply + * MessagePos: int which indicates where in the DataInput is a String that + * was generated by the processing of the reply's request. If this is 0, + * then there is no message. + * length: int indicating the length of the reply's data. + * request-specific data: length bytes of data associated with the reply. + * </pre> + */ + static void processReplies(DataInput in, Message message) { + ArrayList exceptions = new ArrayList(); + boolean optimistic_failure = false; + + try { + try { + Reply.verifyVersionNumber(in); + } catch (JDOFatalUserException ex) { + throw ex; + } + + // Overall status of a reply is FATAL, ROLLBACK, LOGIN, or OK. + Status replyStatus = new Status(in); + if (logger.isDebugEnabled()) { + logger.debug( + "ReplyHandler.hR: replyStatus=" + replyStatus); // NOI18N + } + if (replyStatus.equals(Status.FATAL)) { + // Don't process any replies, just throw a + // JDOFatalDataStoreException so that upper levels can + // rollback, etc. as necessary. + Connector c = message.getConnector(); + if (null != c) { + c.setRollbackOnly(); + } + String str = in.readUTF(); + // sic: DataStoreException. This was decided upon after much + // email discussion. + throw new JDODataStoreException( + msg.msg("ERR_FatalReply", str)); // NOI18N + + } else if (replyStatus.equals(Status.LOGIN)) { + if (logger.isDebugEnabled()) { + logger.debug("ReplyHandler: Login failure"); // NOI18N + } + String str = in.readUTF(); + throw new JDOFatalDataStoreException(str); + + } else if (replyStatus.equals(Status.ROLLBACK)) { + // The rollback was at user request. Don't process any + // replies, but don't throw any exception either. + if (logger.isDebugEnabled()) { + logger.debug( + "ReplyHandler.processReplies: ROLLBACK"); // NOI18N + } + int skipLength = in.readInt(); + in.skipBytes(skipLength); + } else { + // Process each reply. + int numReplies = in.readInt(); + if (logger.isDebugEnabled()) { + logger.debug( + "ReplyHandler.processReplies: numReplies=" + // NOI18N + numReplies); + } + + for (int i = 0; i < numReplies; i++) { + RequestId requestId = new RequestId(in); + Status status = new Status(in); + + if (logger.isDebugEnabled()) { + logger.debug( + "ReplyHandler: " + requestId + // NOI18N + ", " + status); // NOI18N + } + + int messagePos = in.readInt(); + Request request = message.getRequest(requestId); + if (null == request) { + // This could happen if a "nested" request is being + // processed, i.e. this requestId was already removed + // from the list but was not finished processing. + int length = in.readInt(); + in.skipBytes(length); + continue; + /* + throw new FOStoreFatalInternalException( + ReplyHandler.class, "processReplies", // NOI18N + msg.msg( + "ERR_CannotGetRequest", requestId)); // NOI18N + */ + } + int length = in.readInt(); + + if (0 != messagePos) { + if (status.equals(Status.OK) || + status.equals(Status.WARN)) { + + // If there's a message with non-erroneus status, + // let request handle reply. Note: Request + // *MUST* read message in this case! + request.handleReply(status, in, length); + + + // The next 2 cases are similar, differing only + // in whether the user could retry the operation + // or not. In both cases, skip the reply data + // (it might not even be valid, though it's + // length is) and add the message in an + // exception. + } else if (status.equals(Status.FATAL)) { + in.skipBytes(length); + String str = in.readUTF(); + throw new JDOFatalDataStoreException(str); + } else if (status.equals(Status.OPTIMISTIC)) { + // overall status is failed due to optimistic verification + optimistic_failure = true; + // when the result is Status.OPTIMISTIC, the result + // contains the oid of the failed operation + OID oid = OID.read(in); + StateManagerInternal sm = request.getStateManager(); + Object failed = (sm == null) ? null : sm.getObject(); + String str = in.readUTF(); + exceptions.add(new JDOOptimisticVerificationException(str, failed)); + } else { + in.skipBytes(length); + String str = in.readUTF(); + exceptions.add(new JDODataStoreException(str)); + } + } else if (status.equals(Status.ERROR) || + status.equals(Status.FATAL)) { + // If there's no message but the status reports an + // error then we have a bug. + throw new JDOFatalDataStoreException(); + } else { + // No message, non-ERROR status: let request handle + // reply + request.handleReply(status, in, length); + } + } + } + } catch (IOException ex) { + throw new FOStoreFatalIOException( + ReplyHandler.class, "processReplies", ex); // NOI18N + } + + int numExceptions = exceptions.size(); + + if (logger.isDebugEnabled()) { + logger.debug( + "ReplyHandler.processReplies: finished; " + // NOI18N + "numExceptions=" + numExceptions); // NOI18N + } + + if (numExceptions > 0) { + // We can't use exceptions.toArray() because you wouldn't have + // the debugging output. + // XXX use toArray(Throwable[]) + Throwable t[] = new Throwable[numExceptions]; + for (int i = 0; i < numExceptions; i++) { + t[i] = (Throwable)exceptions.get(i); + if (logger.isDebugEnabled()) { + logger.debug("RH.rH: " + t[i]); // NOI18N + } + } + if (optimistic_failure) { + throw new JDOOptimisticVerificationException( + msg.msg("EXC_Optimistic"), t); // NOI18N + } else { + throw new JDODataStoreException( + msg.msg("EXC_Exceptions"), t); // NOI18N + } + } + } +}
Added: incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/Request.java URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/Request.java?view=auto&rev=158176 ============================================================================== --- incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/Request.java (added) +++ incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/Request.java Fri Mar 18 17:02:29 2005 @@ -0,0 +1,54 @@ +/* + * 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.IOException; + +import org.apache.jdo.state.StateManagerInternal; + + +/** + * Represents the ability to send information to the actual file/object store. + * + * @author Dave Bristor + */ +interface Request { + /** Does whatever it is that the kind of request does in actually making a + * request of the store. + * @throws IOException in case of errors with the stream. + */ + public void doRequest() throws IOException; + + /** Processes the results of the effect of the request in the store. To be + * invoked after the store has processed the request, and has returned + * information about that request, such as its status and any accompanying + * data. + * @param in the input stream. + * @param length the length of data in the stream. + * @param status Indication as to the success, failure, etc. of the + * request as handled by the store. + * @throws IOException if any problems reading the stream. + */ + public void handleReply(Status status, DataInput in, int length) + throws IOException; + + /** Get the StateManager associated with this request, null if none. + * @return the StateManager. + */ + public StateManagerInternal getStateManager(); +} Added: incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/RequestFactory.java URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/RequestFactory.java?view=auto&rev=158176 ============================================================================== --- incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/RequestFactory.java (added) +++ incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/RequestFactory.java Fri Mar 18 17:02:29 2005 @@ -0,0 +1,195 @@ +/* + * 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.util.ArrayList; +import java.util.BitSet; + +import org.apache.jdo.pm.PersistenceManagerInternal; +import org.apache.jdo.state.StateManagerInternal; + + +/** + * Creates different kinds of requests. + * + * @author Dave Bristor + */ +interface RequestFactory { + /** + * Creates a request object that will get a datastore OID for a + * provisional OID. + */ + public CreateOIDRequest getCreateOIDRequest( + StateManagerInternal sm, Message m, FOStorePMF pmf, + OID oid, PersistenceManagerInternal pm); + + /** + * Creates a request object to activate the class corresponding to the + * given oid. + * @param cls Class to be activated. + * @param m Message by which the request is to be sent to the store. + * @param pmf FOStorePMF in which the request is taking place. + */ + public ActivateClassRequest getActivateClassRequest( + Class cls, Message m, FOStorePMF pmf); + + /** + * Creates a request object to cause a persistent object to be inserted + * into the datastore. + * @param sm StateManagerInternal of the object to be stored in the + * datastore. + * @param m Message by which the request is to be sent to the store. + * @param pmf FOStorePMF in which the request is taking place. + */ + public InsertRequest getInsertRequest( + StateManagerInternal sm, Message m, FOStorePMF pmf); + + /** + * Creates a request object to cause one or more fields of a persistent + * object to be updated in the store. + * @param sm StateManagerInternal of the object to be updated. + * @param m Message by which the request is to be sent to the store. + * @param pmf FOStorePMF in which the request is taking place. + * @param loadedFields Set of fields loaded from the database. + * @param dirtyFields Set of fields that are to be flushed and + * verified against the those in the database, if this + * <code>update</code> is within the context of an optimistic + * transaction. + * @param optimistic If true, then update is happening in context of + * optimistic transaction, otherwise datastore transaction. + */ + public UpdateRequest getUpdateRequest( + StateManagerInternal sm, Message m, FOStorePMF pmf, + BitSet loadedFields, BitSet dirtyFields, boolean optimistic); + + /** + * Creates a request object to verify that in-memory data is the same as + * that in the database. + * @param sm StateManagerInternal of the object to be verified. + * @param m Message by which the request is to be sent to the store. + * @param pmf FOStorePMF in which the request is taking place. + * @param verifyFields If true, verify values of object, otherwise verify + * only existence (and ignore remaining parameters). + * @param loadedFields Set of fields to be verified against those in the + * database. + */ + public VerifyRequest getVerifyRequest( + StateManagerInternal sm, Message m, FOStorePMF pmf, + boolean verifyFields, BitSet loadedFields); + + /** + * Creates a request object to cause one or more fields of a persistent + * object to be read from the store. + * @param sm StateManagerInternal of the object whose field(s) are to be + * read. + * @param m Message by which the request is to be sent to the store. + * @param pmf FOStorePMF in which the request is taking place. + */ + public FetchRequest getFetchRequest( + StateManagerInternal sm, Message m, FOStorePMF pmf); + + /** + * Creates a request object to cause a particular class's extent to be + * retrieved. + * @param extent FOStoreExtent for which the request is being created. + * @param pcClass Class of the objects whose extent is sought. It is + * <em>required</em> that the caller ensure that the given pcClass + * implement javax.jdo.PersistenceCapable. + * @param subclasses If false, retrieve instances of pcClass only; if true + * retrieve those plus all instances of subclasses of pcClass. + * @param m Message by which the request is to be sent to the store. + * @param pm PersistenceManager on whose behalf the request is taking + * place. + */ + public GetExtentRequest getGetExtentRequest( + FOStoreExtent extent, Class pcClass, + boolean subclasses, Message m, + PersistenceManagerInternal pm); + + /** + * Creates a request to get instances for some oids. + * @param oids List of oids for which instances are needed. + * @param start Starting index in oids for which instances are needed. + * @param numInstances Number of instances which are needed. + * @param pm PersistenceManager on whose behalf the request is taking + * place. + * @param cls Candidate Class for which instances are being obtained. + */ + public GetInstancesRequest getGetInstancesRequest( + ArrayList oids, int start, int numInstances, + Message m, PersistenceManagerInternal pm, Class cls); + + /** + * Creates a request object to cause a persistent object in the store to + * be deleted. + * @param sm StateManagerInternal of the object to delete in the store. + * @param m Message by which the request is to be sent to the store. + * @param pmf FOStorePMF in which the request is taking place. + */ + public DeleteRequest getDeleteRequest( + StateManagerInternal sm, Message m, FOStorePMF pmf); + + /** + * Creates a request object to cause the java.lang.Class associated with + * the given CLID to be provided. + * @param clid CLID of the class that is needed. + * @param m Message by which the request is to be sent to the store. + * @param pmf FOStorePMF in which the request is taking place. + * @param pm PersistenceManager used to load the class. + * place. + */ + public GetClassRequest getGetClassRequest( + CLID clid, Message m, FOStorePMF pmf, PersistenceManagerInternal pm); + + /** + * Creates a request object which notifies the store of the kind of + * transaction that is starting. + * @param m Message by which the request is to be sent to the store. + * @param pmf FOStorePMF in which the request is taking place. + * @param optimistic Indicates whether an optimistic or datastore + * transaction is beginning. + */ + public BeginTxRequest getBeginTxRequest( + Message m, FOStorePMF pmf, boolean optimistic); + + /** + * Creates a request object which causes previous operations to commit. + * @param m Message by which the request is to be sent to the store. + * @param pmf FOStorePMF in which the request is taking place. + */ + public CommitRequest getCommitRequest( + Message m, FOStorePMF pmf); + + /** + * Creates a request object which causes previous operations to rollback. + * @param m Message by which the request is to be sent to the store. + * @param pmf FOStorePMF in which the request is taking place. + */ + public RollbackRequest getRollbackRequest( + Message m, FOStorePMF pmf); + + /** + * Creates a request object to get information from the store. + * @param option Diagnostic parameter code. + * @param className Optional class name. + * @param m Message by which the request is to be sent to the store. + * @param pmf FOStorePMF in which the request is taking place. + */ + public DumpRequest getDumpRequest( + DumpOption option, String className, Message m, + FOStorePMF pmf); +} Added: incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/RequestFinisher.java URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/RequestFinisher.java?view=auto&rev=158176 ============================================================================== --- incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/RequestFinisher.java (added) +++ incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/RequestFinisher.java Fri Mar 18 17:02:29 2005 @@ -0,0 +1,31 @@ +/* + * 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; + +/** + * Means by which a request can indicate that there is work to be done + * once all requests have been processed. + * + * @author Dave Bristor + */ +interface RequestFinisher { + /** + * Does some post-request processing work on behalf of the request which + * created it. + */ + public void finish(); +} Added: incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/RequestHandler.java URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/RequestHandler.java?view=auto&rev=158176 ============================================================================== --- incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/RequestHandler.java (added) +++ incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/RequestHandler.java Fri Mar 18 17:02:29 2005 @@ -0,0 +1,391 @@ +/* + * 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.util.ArrayList; + +import javax.jdo.JDOException; +import javax.jdo.JDOFatalDataStoreException; +import javax.jdo.JDOFatalUserException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.jdo.util.I18NHelper; + +/** +* This dispatches each request received by the store to the appropriate +* request-type-specific request handler. It is very dependent on the +* 'message full of data' means of communicating between client and store. +* +* @author Dave Bristor +*/ +// +// This is server-side code. It does not need to live in the client. +// +abstract class RequestHandler { + /** Subclasses use this Reply instance to send data back to their + * corresponding client-side request. */ + protected final Reply reply; + + /** Length of the data in the Request. */ + protected final int length; + + /** Connection on which the Request arrived. */ + protected final FOStoreServerConnection con; + + /** 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 + + /** Means by which subclasses are created. Each RequestHandler subclass + * should have a static inner class that implements this interface. + */ + interface HandlerFactory { + /** + * @param reply Reply instance into which the returned handler may + * write request/reply - specific information. + * @param length Length in bytes of the request data. + * @param con Connection from which the request was made. + [EMAIL PROTECTED] A RequestHandler instance for a specific subclass of + * RequestHandler. + */ + public RequestHandler getHandler(Reply reply, int length, + FOStoreServerConnection con); + } + + /** + * @param reply Reply to which request handler should write all reply + * information. + * @param length Number of bytes in the connection's input that are for + * this request; subclasses <strong>must</strong> read all bytes so that + * other requests can work. + * @param con Connection from which request handler reads the + * request data. + */ + protected RequestHandler(Reply reply, int length, + FOStoreServerConnection con) { + + this.reply = reply; + this.length = length; + this.con = con; + } + + /** + * The RollbackHandler should override this and return false, so that + * finishers are not run when we are rolling back. + */ + protected boolean getOkToFinish() { + return true; + } + + /** + * The CommitHandler should override this and save the given value, then + * use it to determine whether or not to commit. + */ + protected void setOkToCommit(boolean okToCommit) { } + + /** + * Handles all requests that can be read at this time from the given + * connection. Reads the number of requests, then reads each one and, in + * turn, invokes the handleRequest operation on each. + * <p> + * The data it generates for the client is documented; see [EMAIL PROTECTED] + * ReplyHandler#processReplies}. + * <p> + * If after all requests are thusly handled, none have indicated that the + * second round of handling, called finishing, is not to be done, then + * performs this second round. The finishers that are invoked are + * precisely those which were returned from each request's handleRequest + * invocation, and the finishers have their finish() methods invoked in + * the same order in which the requests were originally invoked. + * <p> + * Finally, writes the number of replies (at the beginning of the reply + * data, in a spot that was set aside for this purpose), and sends replies + * back to the client. + * <p> + * This method is <em>very</em> paranoid about error checking, which + * clutters it up some, but is necessary to ensure that the server keeps + * running. + */ + static void handleRequests(FOStoreServerConnection con) { + // List of finishers to run after all requests have been handled. + // Provides a means for a second phase of operations to run, at the + // request of the requests themselves. Re-initialized each time + // handleRequests is run. + // @see Message#sendToStore for stream header writer. + ArrayList finishers = new ArrayList(); + + DataInput in = con.getInputFromClient(); + int numRequests = 0; + + FOStoreOutput serverData = con.getOutputForClient(); + int numReplies = 0; + + // Result of processing all requests. Written as part of the reply + // data's "header". + Status overallStatus = Status.OK; + + // Position in serverData where the reply's overall status is written. + // Used in case we have to overwrite that value. + // + // Why do we initialize statusPos? + // We have to because if not, the compiler complains that it might + // not have been initialized, at it is used as an R-value. And while + // this is technically true, in fact it is initialized right away + // inside the first try block, and nothing at or before that + // initialization can throw an exception. (Well, at least not + // anything we'd ever recover from.) + int statusPos = 0; + + // Position in serverData wher number of replies is written. + int numRepliesPos; + + // If true, then run finishers after handling all requests. + // This is set below by invoking okToFinish on each RequestHandler + // instance. Only RollbackHandler should return false. + boolean okToFinish = true; + + // The message might contain a CommitRequest. We assume that all + // requests will succeed. But if one of them sets its reply's status + // to Status.ERROR or Status.FATAL, we don't commit. + boolean okToCommit = true; + + try { + Reply.writeVersionNumber(serverData); + + // Write a default overall status of the reply. Will be + // overwritten later if there are any fatal problems in processing + // the requests. + statusPos = serverData.getPos(); + overallStatus.write(serverData); + + // Write a default number of replies. Later write the real number + // of replies. + numRepliesPos = serverData.getPos(); + serverData.writeInt(0); + + try { + Message.verifyVersionNumber(in); + numRequests = in.readInt(); + if (logger.isDebugEnabled()) { + logger.debug("RequestHandler: numRequests=" + // NOI18N + numRequests); + } + } catch (JDOFatalUserException ex) { + // Message version mismatch + throw ex; + } catch (IOException ex) { + throw new FOStoreFatalIOException( + RequestHandler.class, + "handleRequests/numRequests ", ex); // NOI18N + } + + for (int i = 0; i < numRequests; i++) { + Reply reply = null; // Prepare for this iteration. + RequestId requestId = new RequestId(in); + RequestType requestType = new RequestType(in); + + if (logger.isDebugEnabled()) { + logger.debug("RequestHandler: " + // NOI18N + requestId + "/" + // NOI18N + requestType); + } + + HandlerFactory factory = requestType.getHandlerFactory(); + if (null == factory) { + throw new FOStoreFatalInternalException( + RequestHandler.class, "handleRequests", // NOI18N + msg.msg("ERR_CannotCreateHandler", // NOI18N + requestType.toString())); + } + int length = in.readInt(); + reply = con.createReply(requestId); + numReplies++; + + RequestHandler rh = + factory.getHandler(reply, length, con); + + // Run the request, and save it's finisher (if any) + rh.setOkToCommit(okToCommit); + RequestFinisher rf = rh.handleRequest(); + + // Check for commit- and finish- ability + Status replyStatus = reply.getStatus(); + if (Status.ERROR.equals(replyStatus) || + Status.FATAL.equals(replyStatus) || + Status.OPTIMISTIC.equals(replyStatus)) { + + okToCommit = false; + + } else if (Status.ROLLBACK.equals(replyStatus)) { + okToCommit = false; + + // No point in doing work that'll just be undone + okToFinish = false; + + // Mark the status as rolled back. + overallStatus = replyStatus; + int pos = serverData.getPos(); + serverData.setPos(statusPos); + overallStatus.write(serverData); + + // Instead of number of replies, write the number of bytes + // in the reply, so that the client can skip them. + serverData.setPos(numRepliesPos); + serverData.writeInt(pos - numRepliesPos); + + serverData.setPos(pos); + + // Once a rollback has been requested, there's no point in + // processing other replies. + break; + + } else { + okToFinish = rh.getOkToFinish(); + if (okToFinish && null != rf) { + finishers.add(rf); + } + } + } + + if (okToFinish) { + // Run the finishers they produced (if any). + int numFinishers = finishers.size(); + for (int i = 0; i < numFinishers; i++) { + RequestFinisher rf = (RequestFinisher)finishers.get(i); + if (logger.isDebugEnabled()) { + logger.debug( + "RequestHandler.hR: finish " + // NOI18N + rf.getClass().getName()); + } + rf.finish(); + } + } + + // If rollback, numRepliesPos already overwritten with length of + // reply data. + if (! overallStatus.equals(Status.ROLLBACK)) { + // Write the number of replies produced. + int pos = serverData.getPos(); + serverData.setPos(numRepliesPos); + serverData.writeInt(numReplies); + serverData.setPos(pos); + } + + } catch (FOStoreLoginException ex) { + if (logger.isDebugEnabled()) { + logger.debug("Login failure"); // NOI18N + } + try { + int pos = serverData.getPos(); + serverData.setPos(statusPos); + overallStatus = Status.LOGIN; + overallStatus.write(serverData); + serverData.writeUTF(ex.toString()); + int messagePos = serverData.getPos(); + if (messagePos > pos) { + pos = messagePos; + } + serverData.setPos(pos); + } catch (IOException ex2) { + giveUp(ex2); + } + + } catch (Throwable ex) { + // Handle unexpected failure. Inform client that the replies + // could not be processed. Rollback the database. + if (logger.isDebugEnabled()) { + ex.printStackTrace(); + } + try { + int pos = serverData.getPos(); + serverData.setPos(statusPos); + overallStatus = Status.FATAL; + overallStatus.write(serverData); + String message = Reply.getExceptionMessage(ex); + if (null == message) { + message = msg.msg("EXC_Unknown"); // NOI18N + } + serverData.writeUTF(message); + int messagePos = serverData.getPos(); + if (messagePos > pos) { + pos = messagePos; + } + serverData.setPos(pos); + + try { + con.rollback(); + } catch (FOStoreDatabaseException ex2) { + // Oh well, we tried. + } + } catch (IOException ex2) { + giveUp(ex2); + } + + } finally { + try { + con.sendToClient(); + + if (logger.isDebugEnabled()) { + if (Status.OK == overallStatus) { + logger.debug( + "RequestHandler: " + overallStatus + // NOI18N + ", numReplies=" + numReplies); // NOI18N + + } else { + logger.debug( + "RequestHandler: " + overallStatus); // NOI18N + } + } + } catch (IOException ex) { + giveUp(ex); + } catch (FOStoreDatabaseException ex) { + giveUp(ex); + } + } + } + + /** + * Invoke this when all attempts at communicating errors to the client + * have failed. Hope that someone is watching the console! + */ + private static void giveUp(Throwable ex) { + System.err.println(msg.msg("ERR_SendToClient", ex)); // NOI18N + // XXX Should log this, not just print. + ex.printStackTrace(System.err); + } + + /** + * Subclasses implement this to take care of individiual requests. + * @return A RequestFinisher or null. If null, then no further work is + * required on behalf of this request. If a RequestFinisher is returned, + * then it is added to a list, and after all requests have been processed, + * the finishers in the list have their finish() method invoked on them. + * Finishers are invoked in the same order as the requests were. + */ + abstract RequestFinisher handleRequest() + throws IOException, FOStoreDatabaseException; +} + + Added: incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/RequestId.java URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/RequestId.java?view=auto&rev=158176 ============================================================================== --- incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/RequestId.java (added) +++ incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/RequestId.java Fri Mar 18 17:02:29 2005 @@ -0,0 +1,120 @@ +/* + * 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.util.HashMap; + +import javax.jdo.JDOFatalUserException; + +import org.apache.jdo.util.I18NHelper; + + +/** +* Represents a simple id associated with a request. This is used, for +* example, to pair up requests and replies: when a Request is written to the +* store, it is put in a map keyed by RequestId, and when replies are received +* from the store, they contain a RequestId; this is used to find the +* corresponding Request which then handles the reply data. +* +* @author Dave Bristor +*/ +class RequestId { + // RequestId's are per-pmf; this maps from FOStorePMF to IdFactory. + private static HashMap idFactoryTable = new HashMap(); + + // The representation of a RequestId. We use a Long so that we can + // support them as keys in Maps. Note also the need for hashCode() and + // equals() for the same reason. + private final Long id; + + // When the server is having catastrophic problems, it should use this as + // a request id in the reply it creates. + public static final RequestId FAILURE = new RequestId(-1L); + + /** I18N support. */ + private static final I18NHelper msg = I18NHelper.getInstance(I18N.NAME); + + private RequestId(long id) { + this.id = new Long(id); + } + + /** + * Create a new RequestId by reading it's representation from the input. + * @param in DataInput from which representation is read. + */ + RequestId(DataInput in) throws IOException { + this.id = new Long(in.readLong()); + } + + /** + * Writes its representation to the output stream. + * @param out DataOutput stream to which RequestId's representation is + * written. + */ + void write(DataOutput out) throws IOException { + out.writeLong(id.longValue()); + } + + // This is how you allocate yourself a RequestId. Allocates an id from + // the factory corresponding to the given PMF. + // + synchronized static RequestId allocate(FOStorePMF pmf) { + + // IdFactory is in charge of allocating the instances of the + // representation of a RequestId. + class IdFactory { + // Using this might make debug output easier to read: + //private long lastAllocated = 0; + private long lastAllocated = Long.MIN_VALUE; + + long allocate() { + if (lastAllocated == Long.MAX_VALUE) { + throw new FOStoreFatalInternalException( + RequestId.class, "allocate", // NOI18N + msg.msg("ERR_Overflow", new Long(Long.MAX_VALUE))); // NOI18N + } + return lastAllocated++; + } + } + + IdFactory f = (IdFactory)idFactoryTable.get(pmf); + if (null == f) { + f = new IdFactory(); + idFactoryTable.put(pmf, f); + } + return new RequestId(f.allocate()); + } + + /** + * Returns true if the other id is equal to this one. + * @param other RequestId to which this one is compared. + */ + public boolean equals(Object other) { + return id.longValue() == ((RequestId)other).id.longValue(); + } + + public int hashCode() { + return id.hashCode(); + } + + public String toString() { + return "ReqId=" + id.toString(); // NOI18N + } +} Added: incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/RequestType.java URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/RequestType.java?view=auto&rev=158176 ============================================================================== --- incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/RequestType.java (added) +++ incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/RequestType.java Fri Mar 18 17:02:29 2005 @@ -0,0 +1,146 @@ +/* + * 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.util.HashMap; + +/** +* Represents a kind of Request. Used to identify request types between +* client and store; a smaller representation than a Request's +* java.lang.Class. +* +* @author Dave Bristor +*/ +class RequestType { + // We use an Integer so that we can support the requestHandlers HashTable. + private final Integer id; + + private static HashMap requestTypes = new HashMap(); + private static HashMap requestHandlers = new HashMap(); + + private static HashMap debug = new HashMap(); // for debugging only + + private static int nextType = 0; + static { + RequestType rt = null; + + rt = new RequestType(nextType++, ActivateClassRequest.class, + ActivateClassHandler.factory); + + rt = new RequestType(nextType++, InsertRequest.class, + InsertHandler.factory); + + rt = new RequestType(nextType++, UpdateRequest.class, + UpdateHandler.factory); + + rt = new RequestType(nextType++, VerifyRequest.class, + VerifyHandler.factory); + + rt = new RequestType(nextType++, DeleteRequest.class, + DeleteHandler.factory); + + rt = new RequestType(nextType++, FetchRequest.class, + FetchHandler.factory); + + rt = new RequestType(nextType++, GetExtentRequest.class, + GetExtentHandler.factory); + + rt = new RequestType(nextType++, GetInstancesRequest.class, + GetInstancesHandler.factory); + + rt = new RequestType(nextType++, GetClassRequest.class, + GetClassHandler.factory); + + rt = new RequestType(nextType++, CommitRequest.class, + CommitHandler.factory); + + rt = new RequestType(nextType++, RollbackRequest.class, + RollbackHandler.factory); + + rt = new RequestType(nextType++, CreateOIDRequest.class, + CreateOIDHandler.factory); + + rt = new RequestType(nextType++, LoginRequest.class, + LoginHandler.factory); + + rt = new RequestType(nextType++, BeginTxRequest.class, + BeginTxHandler.factory); + + rt = new RequestType(nextType++, DumpRequest.class, + DumpHandler.factory); + } + + RequestType(int id, Class requestClass, + RequestHandler.HandlerFactory factory) { + this.id = new Integer(id); + if (null != requestTypes.put(requestClass, this)) { + throw new FOStoreFatalInternalException( + this.getClass(), "constructor", // NOI18N + "duplicate requestClass entry"); // NOI18N + } + + if (null != requestHandlers.put(this, factory)) { + throw new FOStoreFatalInternalException( + this.getClass(), "constructor", // NOI18N + "duplicate factory entry"); // NOI18N + } + + debug.put(this.id, requestClass); + } + + RequestType(DataInput in) throws IOException { + this.id = new Integer(in.readInt()); + } + + void write(DataOutput out) throws IOException { + out.writeInt(id.intValue()); + } + + /** + * @return The RequestType corresponding to the given class. + */ + static RequestType get(Class cls) { + return (RequestType)requestTypes.get(cls); + } + + public int hashCode() { + return id.hashCode(); + } + + public boolean equals (Object other) { + return this.id.intValue() == ((RequestType)other).id.intValue(); + } + + /** + * @return The factory that can create a RequestHandler corresponding to + * our request type. + */ + RequestHandler.HandlerFactory getHandlerFactory() { + return (RequestHandler.HandlerFactory)requestHandlers.get(this); + } + + public String toString() { + String name = ((Class)(debug.get(id))).getName(); + name = name.substring(name.lastIndexOf('.')+1); + return "ReqType=" + id.toString() + " " + // NOI18N + "(" + name + ")"; // NOI18N + } +} + Added: incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/RollbackHandler.java URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/RollbackHandler.java?view=auto&rev=158176 ============================================================================== --- incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/RollbackHandler.java (added) +++ incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/RollbackHandler.java Fri Mar 18 17:02:29 2005 @@ -0,0 +1,76 @@ +/* + * 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 org.apache.jdo.util.I18NHelper; + + +/** +* Process Rollback requests. +* +* @author Dave Bristor +*/ +// This is server-side code. It does not need to live in the client. +class RollbackHandler extends RequestHandler { + /** I18N support. */ + private static final I18NHelper msg = I18NHelper.getInstance(I18N.NAME); + + private RollbackHandler(Reply reply, int length, + FOStoreServerConnection con) { + + super(reply, length, con); + } + + public static final HandlerFactory factory = + new HandlerFactory() { + public RequestHandler getHandler(Reply reply, int length, + FOStoreServerConnection con) { + return new RollbackHandler(reply, length, con); + }}; + + /** + * Causes RequestHandler.handleRequests to not run finishers when a + * RollbackRequest has been found in a message. There's no reason to run + * them in such a case, as the rollback will undo their effects anyway. + */ + protected boolean getOkToFinish() { + return false; + } + + + RequestFinisher handleRequest() + throws IOException, FOStoreDatabaseException { + + if (logger.isDebugEnabled()) { + logger.debug("RollbackHandler.hR"); // NOI18N + } + + try { + con.rollback(); + } catch (FOStoreDatabaseException ex) { + throw new FOStoreFatalInternalException( + this.getClass(), "handleRequest", // NOI18N + msg.msg("ERR_RollbackFailed", ex)); // NOI18N + } + + con.setOkToReleaseDatabase(true); + reply.setStatus(Status.ROLLBACK); + return null; + } +} Added: incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/RollbackRequest.java URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/RollbackRequest.java?view=auto&rev=158176 ============================================================================== --- incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/RollbackRequest.java (added) +++ incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/RollbackRequest.java Fri Mar 18 17:02:29 2005 @@ -0,0 +1,71 @@ +/* + * 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.IOException; + +/** +* Represents a request to cause operations since the previous Commit or +* Rollback request to rollback. +* +* @author Dave Bristor +*/ +// +// This is client-side code. It does not need to live in the server. +// +class RollbackRequest extends AbstractRequest { + RollbackRequest(Message m, FOStorePMF pmf) { + super(m, pmf); + } + + // + // Methods from AbstractRequest + // + + /** + * Provides the information ecessary for a RollbackRequest. + * The format of this request is (aside from the request header): + * <pre> + * empty: that's right, there's nothing here; the request's existence + * is alone enough to cause a rollback. + * </pre> + */ + protected void doRequestBody() throws IOException { + if (logger.isDebugEnabled()) { + logger.debug("RollbackRequest.dRB"); // NOI18N + } + } + + // + // Methods from Request + // + + /** + * This should never be executed. A RollbackRequest causes the reply + * data's header's Status value to indicate that the user requested a + * rollback, and when the reply handler sees that value, it does not + * invoke handleReply on any replies. + */ + public void handleReply(Status status, DataInput in, int length) + throws IOException { + + if (logger.isDebugEnabled()) { + logger.debug("RollbackRequest.hR"); // NOI18N + } + } +} Added: incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/ShortTranscriber.java URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/ShortTranscriber.java?view=auto&rev=158176 ============================================================================== --- incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/ShortTranscriber.java (added) +++ incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/ShortTranscriber.java Fri Mar 18 17:02:29 2005 @@ -0,0 +1,48 @@ +/* + * 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; + +/** +* Transcribes short values. +* +* @author Dave Bristor +*/ +class ShortTranscriber extends FOStoreTranscriber { + private static ShortTranscriber instance = new ShortTranscriber(); + + private ShortTranscriber() {} + + static ShortTranscriber getInstance() { + return instance; + } + + void storeShort(short value, DataOutput out) throws IOException { + out.writeShort(value); + } + + short fetchShort(DataInput in) throws IOException { + return in.readShort(); + } + + void skip(DataInput in) throws IOException { + in.readShort(); + } +} Added: incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/Status.java URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/Status.java?view=auto&rev=158176 ============================================================================== --- incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/Status.java (added) +++ incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/Status.java Fri Mar 18 17:02:29 2005 @@ -0,0 +1,134 @@ +/* + * 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 org.apache.jdo.util.I18NHelper; + + +/** +* Represents a quick summary of the result of processing a request. +* +* @author Dave Bristor +*/ +class Status { + /** I18N support. */ + private static final I18NHelper msg = I18NHelper.getInstance(I18N.NAME); + + /** Request is being processed. Should be used only by Reply's + * constructor, to write a value which will be overwritten once the + * requests's actual status is known. */ + private static final Status UNKNOWN = new Status(-1); + + /** Request was successfully processed. */ + static final Status OK = new Status(0); + + /** Request succeeded, but with warnings. */ + static final Status WARN = new Status(1); + + /** Request failed. */ + static final Status ERROR = new Status(2); + + /** Request failed fatally in server. */ + static final Status FATAL = new Status(3); + + /** Database rolled back due to user request. */ + static final Status ROLLBACK = new Status(4); + + /** Could not login to database. */ + static final Status LOGIN = new Status(5); + + /** Optimistic failure. Verify, delete, or duplicate oid. */ + static final Status OPTIMISTIC = new Status(6); + + /** Minimum status value. */ + private static final int MIN_STATUS = -1; + + /** Maximum status value. */ + private static final int MAX_STATUS = 6; + + /** Status value. */ + private final int status; + + /** + * Used to create the public static final elements. + */ + private Status(int status) { + this.status = status; + } + + /** + * Used to initialize a reply + */ + static void initialize(Reply reply) throws IOException { + UNKNOWN.write(reply); + } + + /** + * Used to 'reconstitute' a Status value from a DataInput. + */ + Status(DataInput in) throws IOException { + this.status = in.readInt(); + if (status < MIN_STATUS || status > MAX_STATUS){ + throw new FOStoreFatalInternalException( + this.getClass(), "constructor(DataInput)", // NOI18N + msg.msg("ERR_OutOfRange", new Integer(status), // NOI18N + new Integer(MIN_STATUS), new Integer(MAX_STATUS))); + } + } + + /** + * Used to externalize a Status value. + */ + void write(DataOutput out) throws IOException { + out.writeInt(status); + } + + /** + * Returns length of a Status value's representation in bytes. + */ + int getLength() { + return 4; + } + + /** + * Compares this to another Status. + */ + public boolean equals(Object other) { + return this.status == ((Status)other).status; + } + + public String toString() { + String rc = msg.msg("MSG_Invalid"); // NOI18N + switch (status) { + case -1: rc = msg.msg("MSG_Unknown"); break; // NOI18N + case 0: rc = msg.msg("MSG_Ok"); break; // NOI18N + case 1: rc = msg.msg("MSG_Warn"); break; // NOI18N + case 2: rc = msg.msg("MSG_Error"); break; // NOI18N + case 3: rc = msg.msg("MSG_Fatal"); break; // NOI18N + case 4: rc = msg.msg("MSG_Rollback"); break; // NOI18N + case 5: rc = msg.msg("MSG_Login"); break; // NOI18N + case 6: rc = msg.msg("MSG_Optimistic"); break; // NOI18N + default: break; + } + return rc; + } +} + Added: incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/SubclassSet.java URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/SubclassSet.java?view=auto&rev=158176 ============================================================================== --- incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/SubclassSet.java (added) +++ incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/SubclassSet.java Fri Mar 18 17:02:29 2005 @@ -0,0 +1,115 @@ +/* + * 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.DataInputStream; +import java.io.DataOutputStream; +import java.io.InputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.HashSet; +import java.util.Iterator; + +import org.netbeans.mdr.persistence.Streamable; +import org.netbeans.mdr.persistence.StorageException; +import org.netbeans.mdr.persistence.StorageIOException; + +/** +* Represents a set of CLIDs of subclasses of a given class. +* <p> +* This class is <code>public</code> so that it can be used as a +* <code>Streamable</code> and stored in the database. +* +* @author Dave Bristor +*/ +public class SubclassSet implements Streamable { + /** The oid of this list of subclasses. */ + private OID oid; + + /** List of the clids which represent classes that are subclasses of the + * class indicated by the CLID in our oid. */ + private HashSet clids; + + /** + * Given clid is the first entry in the list. + */ + private SubclassSet(OID oid, CLID clid) { + this.oid = oid; + clids = new HashSet(); + clids.add(clid); + } + + static SubclassSet create(OID oid, CLID clid) { + return new SubclassSet(oid, clid); + } + + /** + * Add the given clid to the list. + */ + void add(CLID clid) { + clids.add(clid); + } + + /** + * @return Iterator over the CLID's in this subclass list. + */ + Iterator iterator() { + return clids.iterator(); + } + + // + // Implement Streamable + // + + public SubclassSet() { } + + /** + * Write this SubclassSet to the given stream. + */ + public void write(OutputStream os) throws StorageException { + DataOutputStream dos = new DataOutputStream(os); + + try { + oid.write(dos); + int size = clids.size(); + dos.writeInt(size); + for (Iterator i = clids.iterator(); i.hasNext();) { + CLID clid = (CLID)i.next(); + clid.write(dos); + } + } catch (IOException ex) { + throw new StorageIOException(ex); + } + } + + /** + * Initialize this SubclassSet from the given stream. + */ + public void read(InputStream is) throws StorageException { + DataInputStream dis = new DataInputStream(is); + try { + this.oid = OID.read(dis); + int size = dis.readInt(); + clids = new HashSet(size); + for (int i = 0; i < size; i++) { + clids.add(CLID.read(dis)); + } + } catch (IOException ex) { + throw new StorageIOException(ex); + } + } +} Added: incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/Tester.java URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/Tester.java?view=auto&rev=158176 ============================================================================== --- incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/Tester.java (added) +++ incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/Tester.java Fri Mar 18 17:02:29 2005 @@ -0,0 +1,132 @@ +/* + * 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.text.NumberFormat; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** +* Provides a relatively convient way to print debug messages. +* +* @author Dave Bristor +*/ +class Tester { + static final Log logger = LogFactory.getFactory().getInstance( + "org.apache.jdo.impl.fostore"); // NOI18N + + private static boolean threadPrinting = + Boolean.getBoolean("org.apache.jdo.impl.fostore.threadprinting"); + + private static final NumberFormat nf; + static { + nf = NumberFormat.getInstance(); + nf.setMinimumIntegerDigits(4); + nf.setMaximumIntegerDigits(4); + nf.setMaximumFractionDigits(0); + nf.setGroupingUsed(false); + } + + // This method checks that TIME is enabled, unlike the other methods. + // Reason: it doesn't take an arguments, and therefore the runtime of + // calling this is very small (no String args to construct as w/ print + // methods). + static Object startTime() { + Timer timer = new Timer(); + timer.start(); + return timer; + } + + static void printTime(Object o, String msg) { + if (null != o && o instanceof Timer) { + Timer timer = (Timer)o; + timer.println(msg); + } + } + + static String toHex(long n, int len) { + String rc = Long.toHexString(n); + StringBuffer zeroes = new StringBuffer("0000000000000000"); // NOI18N + int length = rc.length(); + if (length > len) { + rc = rc.substring(length - len); + } else if (len > length) { + rc = zeroes.substring(0, len - length) + rc; + } + return rc; + } + + static void dump(String label, byte data[], int length) { + dump(label, data, 0, length); + + } + + static void dump(String label, byte data[], int offset, int length) { + // Determine number of lines to print + final int bytesPerLine = 16; + int lines = length / bytesPerLine; + if (0 != length % bytesPerLine) { + lines++; + } + int line = 0; // Address at start of line + + int addr = offset; + int max = offset + length; + + logger.trace("dumping " + length + " bytes"); // NOI18N + + for (int i = 0; i < lines; i++) { + StringBuffer buf = new StringBuffer(); + + if (threadPrinting) { + buf.append(Thread.currentThread().toString() + ": "); // NOI18N + } + buf.append(label + " " + nf.format((long)line) + ": "); // NOI18N + line += bytesPerLine; + for (int j = 0; j < bytesPerLine; j++) { + if (addr >= max) { + break; + } else { + buf.append(toHex((long)data[addr], 2) + " "); // NOI18N + } + addr++; + } + logger.trace(buf.toString()); + } + } + + static void dump(String label, FOStoreOutput out, int offset, int length) { + byte data[] = out.getBuf(); + dump(label, data, offset, length); + } + + static class Timer { + long start; + + public void start() { + start = System.currentTimeMillis(); + } + + public void println(String msg) { + long end; + end = System.currentTimeMillis(); + System.out.println(msg + ": " + (end - start)); // NOI18N + start = end; + } + } +} Added: incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/UpdateHandler.java URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/UpdateHandler.java?view=auto&rev=158176 ============================================================================== --- incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/UpdateHandler.java (added) +++ incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/UpdateHandler.java Fri Mar 18 17:02:29 2005 @@ -0,0 +1,211 @@ +/* + * 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.util.Arrays; + +import javax.jdo.JDOUserException; + +import org.apache.jdo.util.I18NHelper; + + +/** +* Process requests to update instances in the datastore. +* +* @author Dave Bristor +*/ +// +// This is server-side code. It does not need to live in the client. +// +class UpdateHandler extends InsertHandler { + /** I18N Support */ + // Note that in this file we're using keys with the "EXC" prefix, but + // outside the context of throwing an exception: this is because the + // client side will take these messages and put them into exceptions. See + // ReplyHandler.handleReplies. + private static final I18NHelper msg = I18NHelper.getInstance(I18N.NAME); + + /** Construct a new handler for processing updates. + * @param reply the Reply. + * @param length the length of data in the stream. + * @param con the connection to use. + */ + protected UpdateHandler(Reply reply, int length, + FOStoreServerConnection con) { + + super(reply, length, con); + } + + static final HandlerFactory factory = + new HandlerFactory() { + public RequestHandler getHandler(Reply reply, int length, + FOStoreServerConnection con) { + return new UpdateHandler(reply, length, con); + }}; + + RequestFinisher handleRequest() + throws IOException, FOStoreDatabaseException { + + RequestFinisher rc = null; + FOStoreInput in = con.getInputFromClient(); + + // True if verify() returns true or datastore transaction. + boolean okToUpdate = true; + + // True if verify throws an exception. In that case, okToUpdate + // *will* be false. + boolean verifyException = true; + + if (logger.isDebugEnabled()) { + logger.debug("UpdateHandler.hR: begin"); // NOI18N + } + + OID oid = null; + try { + boolean optimistic = in.readBoolean(); + if (optimistic) { + oid = OID.read(in); + Block block = readBlock(in); + okToUpdate = verify(oid, true, block); + } + verifyException = false; + } catch(DoesNotExistException ex) { + okToUpdate = false; + reply.writeOID(oid); + reply.setStatus( + Status.OPTIMISTIC, + msg.msg("EXC_OptimisticDoesNotExist", oid)); // NOI18N + } catch(Exception ex) { + okToUpdate = false; + reply.writeOID(oid); + reply.setStatus( + Status.ERROR, + msg.msg("ERR_VerifyException", oid), ex); // NOI18N + } + + int length = in.readInt(); + if (okToUpdate) { + // datastore image exists and matches before image + rc = super.handleRequest(); + } else { + in.skipBytes(length); + if ( ! verifyException) { + reply.writeOID(oid); + // mismatch between before image and datastore image + // status has not yet been set by exception handling + reply.setStatus( + Status.OPTIMISTIC, + msg.msg("EXC_OptimisticVerifyFailed", oid)); // NOI18N + } + } + + if (logger.isDebugEnabled()) { + logger.debug("UpdateHandler.hR: end"); // NOI18N + } + + return rc; + } + + /** Verify before image versus database state of an object. + * @return true if verify succeeds as per <code>verifyFields</code>. + * @param oid OID of object to be verified + * @param verifyFields If true, verify that values of the data + * corresponding to <code>oid</code> in the database match those in the + * given block; if false verify that object corresponding to + * <code>oid</code> exists. + * @param block Block of data for value verification; may be null if + * <code>verifyFields</code> is false. + * @throws IOException if stream errors. + * @throws FOStoreDatabaseException if any errors in the database. + * @throws DoesNotExistException if the object to be updated does not exist. + */ + protected boolean verify(OID oid, boolean verifyFields, Block block) + throws IOException, FOStoreDatabaseException, DoesNotExistException { + + if (logger.isDebugEnabled()) { + logger.debug("UpdateHandler.verify for: " + oid + // NOI18N + ", verifyFields=" + verifyFields); // NOI18N + } + + boolean rc = false; + + FOStoreDatabase fodb = con.getDatabase(); + Block current = (Block)fodb.getIfExists(oid); + + rc = (null != current); + + if (verifyFields) { + byte blockData[] = block.getData(); + + if (logger.isTraceEnabled()) { + Tester.dump("BLK", blockData, blockData.length); // NOI18N + } + + // Reset to false so that if we get an error, client won't try to + // set status. + rc = false; + + if (null == current) { + throw new DoesNotExistException(); + } else { + byte currentData[] = current.getData(); + if (logger.isTraceEnabled()) { + logger.debug( + "UpdateHandler.verify current data:"); // NOI18N + Tester.dump("CUR", currentData, currentData.length); // NOI18N + } + + // XXX TBD Compare and report based on field values + rc = Arrays.equals(blockData, currentData); + } + } + + if (logger.isDebugEnabled()) { + logger.debug("UpdateHandler.verify returns: " + rc); // NOI18N + } + + return rc; + } + + /** + * Thrown by [EMAIL PROTECTED] UpdateHandler#verify} if an object which should exist + * in the database, does not. + */ + private class DoesNotExistException extends Exception { } + + /** Replace a block in the database. + * @see InsertHandler#updateDB + * @param provOID provisional OID. + * @param realOID OID to use as key in the database. + * @param block Block to be inserted in database. + * @param db Database into which block is replaced. + * @throws IOException for stream errors. + * @throws FOStoreDatabaseException for any database error except for object not found. + */ + protected void updateDB(OID realOID, OID provOID, Block block, + FOStoreDatabase db) + throws IOException, FOStoreDatabaseException { + + if (logger.isDebugEnabled()) { + logger.debug("UpdateHandler.updateDb: " + realOID); // NOI18N + } + db.replace(realOID, block); + } +} Added: incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/UpdateRequest.java URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/UpdateRequest.java?view=auto&rev=158176 ============================================================================== --- incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/UpdateRequest.java (added) +++ incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/UpdateRequest.java Fri Mar 18 17:02:29 2005 @@ -0,0 +1,144 @@ +/* + * 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.IOException; +import java.util.BitSet; + +import javax.jdo.PersistenceManager; +import javax.jdo.Transaction; + +import org.apache.jdo.model.jdo.PersistenceModifier; +import org.apache.jdo.state.StateManagerInternal; +import org.apache.jdo.util.I18NHelper; + + + +/** + * Represents a request to change one or more fields of a persistent object in + * the store. + * + * @author Dave Bristor + */ +// +// This is client-side code. It does not need to live in the server. +// +class UpdateRequest extends InsertRequest { + /** Fields which are to be updated. */ + private final BitSet loadedFields; + + /** Fields which are to be verified in database, in case of + * optimistic transaction. */ + private final BitSet dirtyFields; + + /** If true, update happens in context of an optimistic transaction. */ + private final boolean optimistic; + + UpdateRequest(StateManagerInternal sm, Message m, FOStorePMF pmf, + BitSet loadedFields, BitSet dirtyFields, boolean optimistic) { + + super(sm, m, pmf); + this.loadedFields = loadedFields; + this.dirtyFields = dirtyFields; + this.optimistic = optimistic; + } + + // + // Methods from AbstractRequest + // + + /** + * Provides the information necessary to do an UpdateRequest. + * The format of this request is (aside from the request header): + * <pre> + * optimistic: boolean + * oid: OID + * data block + * length: int + * InsertRequest's doRequestBody - written data + * </pre> + * The oid and data block are written only if optimistic is true. + * @see AbstractRequest#doRequestBody + */ + protected void doRequestBody() throws IOException { + OID oid = (OID)sm.getInternalObjectId(); + if (oid.isProvisional()) { + throw new FOStoreFatalInternalException( + this.getClass(), "doRequestBody", // NOI18N + msg.msg("ERR_OidIsProvisional", oid)); // NOI18N + } + + if (logger.isDebugEnabled()) { + logger.debug("UpdateRequest.dRB: begin: " + optimistic); // NOI18N + } + + out.writeBoolean(optimistic); + + if (optimistic) { + oid.write(out); + // XXX For now, verify the values of all the fields. + writeBlock(jdoClass.getPersistentFieldNumbers(), true); + } + + // Save space to write the length of the data written by + // InsertRequest. The reason is, that on the Handler side, if we're + // running an optimistic transaction and the verify fails, we just + // want to skip over InsertRequest's bytes. + // + int lengthPos = out.getPos(); + out.writeInt(LENGTH_COOKIE); + int startPos = out.getPos(); + + super.doRequestBody(); + + int currentPos = out.getPos(); + int length = currentPos - startPos; + if (logger.isDebugEnabled()) { + logger.debug("UpdateRequest.dRB: length=" + length); // NOI18N + } + out.setPos(lengthPos); + out.writeInt(length); + out.setPos(currentPos); + + if (logger.isDebugEnabled()) { + logger.debug("UpdateRequest.dRB: end"); // NOI18N + } + } + + // + // Methods from Request + // + + /** + * Handles reply data from an UpdateReply. + * The format of this reply is + * <pre> + * oid: OID + * </pre> + */ + public void handleReply(Status status, DataInput in, int length) + throws IOException { + + OID replyOid = OID.read(in); + + if (logger.isDebugEnabled()) { + logger.debug("UpdateRequest.hR: " + replyOid + // NOI18N + ", " + status); // NOI18N + } + } +} Added: incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/VerifyHandler.java URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/VerifyHandler.java?view=auto&rev=158176 ============================================================================== --- incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/VerifyHandler.java (added) +++ incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/VerifyHandler.java Fri Mar 18 17:02:29 2005 @@ -0,0 +1,97 @@ +/* + * 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.util.Arrays; + +import javax.jdo.JDOUserException; + +import org.apache.jdo.util.I18NHelper; + + +/** +* Process requests to verify instances in the datastore. +* +* @author Dave Bristor +*/ +// +// This is server-side code. It does not need to live in the client. +// +class VerifyHandler extends UpdateHandler { + /** I18N Support */ + // Note that in this file we're using keys with the "EXC" prefix, but + // outside the context of throwing an exception: this is because the + // client side will take these messages and put them into exceptions. See + // ReplyHandler.handleReplies. + private static final I18NHelper msg = I18NHelper.getInstance(I18N.NAME); + + private VerifyHandler(Reply reply, int length, + FOStoreServerConnection con) { + + super(reply, length, con); + } + + static final HandlerFactory factory = + new HandlerFactory() { + public RequestHandler getHandler(Reply reply, int length, + FOStoreServerConnection con) { + return new VerifyHandler(reply, length, con); + }}; + + /** + * Verify that instance exists and/or its values match those in the + * client. + */ + RequestFinisher handleRequest() + throws IOException, FOStoreDatabaseException { + + FOStoreInput in = con.getInputFromClient(); + + if (logger.isDebugEnabled()) { + logger.debug("VerifyHandler.hR: begin"); // NOI18N + } + + boolean rc = false; + + OID oid = OID.read(in); + boolean verifyFields = in.readBoolean(); + + try { + if (verifyFields) { + Block block = readBlock(in); + rc = verify(oid, true, block); + } else { + rc = verify(oid, false, null); + } + reply.writeBoolean(rc); + reply.setStatus(Status.OK); + } catch (Exception ex) { + reply.writeBoolean(false); + reply.setStatus(Status.ERROR, + msg.msg("ERR_VerifyException", oid, ex)); // NOI18N + } + + if (logger.isDebugEnabled()) { + logger.debug("VerifyHandler.hR: end, rc=" + rc); // NOI18N + } + + return null; + } +}