blob: f9327c30d1906ee8a296080f76852f1f6d7658e1 [file] [log] [blame]
/*
* 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
}
}
}
}