Hi all, Together with the "hook in PreferredClassProvider to determine class boomerang" I think I come a long way towards being able to evolve persistent services in time, as well as the ability to provide more context information in case a client can't communicate with the server due to maintenance/upgrades e.g..
However so far what I've been working on doesn't provide custom exceptions that could be utilized by a client to react upon, i.e. to distinguish between some failure cases for which the current set of RemoteExceptions are not adequate IMHO. Therefore I now came up with 2 custom RemoteExceptions that I need to be able to develop client side failure handling logic, either internally for usage by specialized proxies or directly for client side utilities. As they are not specific to my Platform and could be seen as general I believe they belong in the net.jini namespace (net.jini.jeri ?). Attached you will find the code for them, although they are still in the org.cheiron.seven.proxy namespace. I decided to extend current subclasses of RemoteException and not to introduce a new branch in the RemoteException hierarchy to be able to play nice with current common failure handling logic out in the field. Although I'm not sure whether this is the best thing to do, or whether I took the right RemoteExceptions as a base class, any input is welcome. First we have the OfflineException to be thrown by the RMI runtime when a remote method invocation is performed for a service that is currently off-line but denoted as a persistent service and as such is expected to be on-line at some point in the future. The exception allows you to specify the expected on-line date so client retry logic can take this into account. Clients can utilize this exception to decide whether they want to retry, if so when to perform the first retry. If you look for example to a lookup server implementation, and there is a remote event listener registered and the lookup server encounters this exception it can check whether the on-line time is beyond the time the associated lease expires, if this is the case it could drop the event registration directly saving itself from performing retries and consuming resources to keep the events in memory, persist them, etc. A transaction manager service can utilize this exception to schedule any invocation on TransactionParticipant and so on. The other exception is the ObjectIncompatibleException that is thrown when the specialized proxy has a particular version of mobile code that is not compatible with the version of the remote object and that such an invocation is not allowed to take place (due to an evolving codebase e.g.). As part of the exception it is possible to pass in an object that might allow the client to restore proper communication with the remote object. As an example, if a transaction manager service performs a call to TransactionParticipant and ObjectIncompatibleException is thrown it knows that the transaction participant proxy is no longer able to communicate properly with its backend even while it is on-line, therefore this is a definite failure. Although if ObjectIncompatibleException.getProxy() returns an object that implements TransactionParticipant it can utilize that proxy (likely after proxy preparation) to continue its operations against the transaction participant. I'm curious to find out about how others think of these 2 exceptions. -- Mark
/* * $Header$ * * Copyright 2007 Virgil BV. * * 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.cheiron.seven.proxy; import java.io.IOException; import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.rmi.ConnectException; import java.util.Date; /** * An <code>OfflineException</code> is thrown when the RMI runtime receives a * remote method invocation for a remote object that is currently off-line and * which might become on-line at some point in the future. * <p> * This exception can be used to provide a client performing a remote method * invocation with information about the ability to serve that remote method * invocation at a later date, this is particularly important for services that * maintain state and for which it is essential a client is able to complete * the operation, such as with a transaction manager service. * * @version $Revision$ $Date$ */ public class OfflineException extends ConnectException { /** * Constructs a <code>OfflineException</code> that returns <code>null</code> * for an invocation of [EMAIL PROTECTED] #getOnlineDate()}. * * @param message detail message */ public OfflineException(String message) { this(message, null); } /** * Constructs a <code>OfflineException</code> that returns the date the * remote object is expected to be online. * * @param message detail message * @param onlineDate date the remote object is expected to be on-line for * serving remote method invocations, or <code>null</code> if no such * date can be estimated */ public OfflineException(String message, Date onlineDate) { super(message); date = (onlineDate == null) ? Long.MIN_VALUE : onlineDate.getTime(); } /** * Returns the absolute date on which the remote object is expected to be * online to serve remote method invocations. * <p> * The absolute date is relative to the local clock on which this exception * is unmarshalled. * * @return date on which the remote object is expected to be online for * serving remote method invocations, or <code>null</code> when no such * date can be provided */ public Date getOnlineDate() { return (date == Long.MIN_VALUE) ? null : new Date(date); } /** * Reads the object from stream and verifies whether the invariants are * satisfied. * * @param in stream to read object from * * @throws ClassNotFoundException if the class of a serialized object * could not be found * @throws InvalidObjectException if the object has been corrupted, * invariants are not satisfied * @throws IOException if an I/O error occurs */ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); long interval = in.readLong(); date = (interval == Long.MIN_VALUE) ? interval : (System.currentTimeMillis() + interval); // check for overflow if (date < 0 && interval > 0) { date = Long.MAX_VALUE; } } /** * Throws an <code>InvalidObjectException</code> in case a subclass of * this class is deserialized and the stream doesn't contain the class * descriptor for this class. * * @throws InvalidObjectException if stream doesn't contain class descriptor * for this class */ private void readObjectNoData() throws InvalidObjectException { throw new InvalidObjectException("no data in stream"); } /** * Writes the serialized data for this object in a custom format. * * @param out stream to write serialized data to * * @throws IOException if I/O errors occur while writing to the * underlying <code>OutputStream</code> * * @serialData writes the default serialized state and after that the * number of milliseconds between the date the remote object is * expected to be on-line and the current time with * [EMAIL PROTECTED] ObjectOutputStream#writeLong(long)}. In case the online * date is unknown [EMAIL PROTECTED] Long#MIN_VALUE} is written to the stream. */ private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); out.writeLong((date == Long.MIN_VALUE) ? date : (date - System.currentTimeMillis())); } /** Stream unique identifier of class. */ private static final long serialVersionUID = -6575534814776517939L; /** * Date the remote object is expected to be online for serving remote method * invocations, <code>Long.MIN_VALUE</code> is used to indicate the online * date is unknown. */ private transient long date; }
/* * $Header$ * * Copyright 2007 Virgil BV. * * 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.cheiron.seven.proxy; import java.io.IOException; import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.rmi.NoSuchObjectException; import java.util.Collection; import java.util.Iterator; import net.jini.io.MarshalledInstance; import net.jini.io.ObjectStreamContext; import net.jini.io.context.IntegrityEnforcement; /** * An <code>ObjectIncompatibleException</code> is thrown when a remote method * invocation is performed from a specialized proxy with a particular version of * mobile code that is not compatible with the version of the remote object and * that such an invocation is not allowed to happen. * <p> * In case the exception provides a non-<code>null</code> return value for * [EMAIL PROTECTED] #getProxy()} a proxy is provided of the proper version of mobile code * that might allow a client to continue its operation with the remote object. * In an ideal situation this proxy represent the same object as the object * through which the remote method invocation on the remote object was * initiated, although in many cases this will likely be the main proxy for a * Jini service. * <p> * If an <code>ObjectIncompatibleException</code> occurs attempting to invoke a * method on the remote object, the call may be retransmitted on the and still * preserve RMI's "at most once" call semantics. * * @version $Revision$ $Date$ */ public class ObjectIncompatibleException extends NoSuchObjectException { /** * Constructs an <code>ObjectIncompatibleException</code> that returns * <code>null</code> for an invocation of [EMAIL PROTECTED] #getProxy()}. * * @param message detail message */ public ObjectIncompatibleException(String message) { this(message, null); } /** * Constructs an <code>ObjectIncompatibleException</code> that returns * a compatible proxy to the remote object that might allow a client to * restore proper communication with the remote object. * * @param message detail message * @param proxy optional proxy to remote object that might allow a client * to restore proper communication with the remote object, can be * <code>null</code> */ public ObjectIncompatibleException(String message, MarshalledInstance proxy) { super(message); this.proxy = proxy; } /** * Returns the proxy that has the proper version of mobile code communicate * with the remote object for which the remote method invocation failed. * <p> * There are no guarantees the proxy returned, if any, is the same object * as that caused this exception to be thrown. When it is not possible to * return the same object it is expected an object is returned that allows * the client to recreate the object that caused this exception, although * this is no requirement. * * @return proxy to remote object, or <code>null</code> if no such object * can be provided and this failure must be considered definite * * @throws ClassNotFoundException if any classes necessary for * reconstructing the proxy to the remote object can't be found or * if codebase integrity is required and the integrity of the object's * codebase can't be confirmed * @throws IOException if an <code>IOException</code> occurs while * deserializing the object from its internal representation */ public Object getProxy() throws ClassNotFoundException, IOException { if (proxy == null) { return null; } return proxy.get(integrity); } /** * Reads the object from stream and verifies whether the invariants are * satisfied. * * @param in stream to read object from * * @throws ClassNotFoundException if the class of a serialized object * could not be found * @throws InvalidObjectException if the object has been corrupted, * invariants are not satisfied * @throws IOException if an I/O error occurs */ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); // retrieve the value of the integrity flag if (in instanceof ObjectStreamContext) { Collection ctx = ((ObjectStreamContext) in).getObjectStreamContext(); for (Iterator i = ctx.iterator(); i.hasNext(); ) { Object obj = i.next(); if (obj instanceof IntegrityEnforcement) { integrity = ((IntegrityEnforcement) obj).integrityEnforced(); break; } } } } /** * Throws an <code>InvalidObjectException</code> in case a subclass of * this class is deserialized and the stream doesn't contain the class * descriptor for this class. * * @throws InvalidObjectException if stream doesn't contain class descriptor * for this class */ private void readObjectNoData() throws InvalidObjectException { throw new InvalidObjectException("no data in stream"); } /** Stream unique identifier of class. */ private static final long serialVersionUID = 8989556436312298846L; /** * Flag related to the verification of codebase integrity. * <p> * A value of <code>true</code> indicates the last time this event was * unmarshalled, the enforcement of codebase integrity was in effect. */ private transient boolean integrity; /** * Optional object that represents a proxy related to the remote object * for which this exception is thrown, can be <code>null</code>. * * @serial */ private final MarshalledInstance proxy; }
