Explicit Serialization API and Security
Peter Firmstone
peter.firmstone at zeus.net.au
Thu Jan 29 11:27:38 UTC 2015
Two examples:
1. of a child class with super class invariants and
2. Checking List and Map contents for type correctness.
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 net.jini.lease;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.rmi.MarshalledObject;
import net.jini.core.event.RemoteEvent;
import net.jini.core.lease.Lease;
import org.apache.river.api.io.AtomicSerial;
import org.apache.river.api.io.AtomicSerial.GetArg;
/**
* Event generated by a lease renewal set when its lease is about to
* expire.
*
* @author Sun Microsystems, Inc.
* @see LeaseRenewalSet
*/
@AtomicSerial
public class ExpirationWarningEvent extends RemoteEvent {
private static final long serialVersionUID = -2020487536756927350L;
private static GetArg check(GetArg arg) throws IOException {
RemoteEvent sup = new RemoteEvent(arg);
if (sup.getID() != LeaseRenewalSet.EXPIRATION_WARNING_EVENT_ID)
throw new InvalidObjectException("Illegal object state");
return arg;
}
public ExpirationWarningEvent(GetArg arg) throws IOException {
super(check(arg));
}
/**
* Simple constructor. Note event id is fixed to
* <code>LeaseRenewalSet.EXPIRATION_WARNING_EVENT_ID</code>.
*
* @param source the <code>LeaseRenewalSet</code> that generated the
* event
* @param seqNum the sequence number of this event
* @param handback the <code>MarshalledObject</code> passed in as
* part of the event registration
*/
public ExpirationWarningEvent(LeaseRenewalSet source,
long seqNum,
MarshalledObject handback)
{
super(source, LeaseRenewalSet.EXPIRATION_WARNING_EVENT_ID, seqNum,
handback);
}
/**
* Convenience method to retrieve the <code>Lease</code> associated
* with the source of this event. This is the <code>Lease</code>
* which is about to expire.
* <p>
* The <code>Lease</code> object returned will be equivalent (in the
* sense of <code>equals</code>) to other <code>Lease</code> objects
* associated with the set, but may not be the same object. One
* notable consequence of having two different objects is that the
* <code>getExpiration</code> method of the <code>Lease</code>
* object returned by this method may return a different time than
* the <code>getExpiration</code> methods of other
* <code>Lease</code> objects granted on the same set.
* <p>
* The expiration time associated with the <code>Lease</code> object
* returned by this method will reflect the expiration the lease had
* when the event occurred. Renewal calls may have changed the
* expiration time of the underlying lease between the time when the
* event was generated and when it was delivered.
*
* @return the lease associated with the source of this event
*/
public Lease getRenewalSetLease() {
return ((LeaseRenewalSet) getSource()).getRenewalSetLease();
}
}
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 net.jini.discovery;
import com.sun.jini.proxy.MarshalledWrapper;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.rmi.MarshalledObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import net.jini.core.event.RemoteEvent;
import net.jini.core.lookup.ServiceID;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.io.MarshalledInstance;
import org.apache.river.api.io.AtomicSerial.GetArg;
/**
* Whenever the lookup discovery service discovers or discards a lookup
* service matching the discovery/discard criteria of one or more of its
* registrations, the lookup discovery service sends an instance of this
* class to the listener corresponding to each such registration.
* <p>
* For each registration created by the lookup discovery service, an event
* identifier is generated. That event identifier uniquely maps the
* registration to the listener (submitted by the client to the lookup
* discovery service during the registration process) and to the set of
* groups and locators the client is interested in discovering. The event
* identifier is unique across all other active registrations with the
* lookup discovery service, and is sent to the listener as part of the
* event.
* <p>
* Because clients of the lookup discovery service need to know not only
* when a targeted lookup service has been discovered, but also when it
* has been discarded, the lookup discovery service uses an instance of
* this class to notify a client's registration(s) when either of these
* events occurs.
* <p>
* This class extends RemoteEvent, adding the following additional items
* of abstract state: a boolean indicating whether the lookup services
* referenced by the event have been discovered or discarded; and a set
* consisting of proxy objects where each proxy is a marshalled instance
* of the ServiceRegistrar interface, and each is a proxy of one of the
* recently discovered or discarded lookup service(s). Methods are defined
* through which this additional state may be retrieved upon receipt of an
* instance of this class.
* <p>
* The sequence numbers for a given event identifier are "strictly
* increasing". This means that when any two such successive events
* have sequence numbers differing by only a value of 1, then it is
* guaranteed that no events have been missed. On the other hand, when
* viewing the set of received events in order, if the difference
* between the sequence numbers of two successive events is greater
* than 1, then one or more events may or may not have been missed.
* For example, a difference greater than 1 could occur if the lookup
* discovery service crashes, even if no events are lost because of
* the crash. When two successive events have sequence numbers whose
* difference is greater than 1, there is said to be a "gap" between
* the events.
* <p>
* When a gap occurs between events, the state of the locally managed
* set of lookup services may or may not fall "out of sync" with the
* corresponding remote state. For example, if the gap corresponds to
* a missed event representing the (initial) discovery of a targeted
* lookup service, the remote state will reflect this discovery whereas
* the local state will not. When such a situation occurs, clients may
* wish to employ the methods of the corresponding registration object
* to query the current remote state in order to update the current
* local state.
* <p>
* Thus, clients typically use this class to determine if conditions
* are right for a loss of synchronization (by verifying the existence
* of a gap in the event sequence). Clients then typically use the
* methods provided by the registration object to both determine if a
* loss of synchronization has actually occurred, and to correct
* such a situation when it does occur.
*
* @author Sun Microsystems, Inc.
*
* @see net.jini.core.event.RemoteEvent
* @see net.jini.core.lookup.ServiceRegistrar
*/
public class RemoteDiscoveryEvent extends RemoteEvent {
private static final long serialVersionUID = -9171289945014585248L;
/**
* Flag indicating whether the event is a discovery event or a discard
* event. If this variable is <code>false</code>, then the lookup
services
* referenced by this event were just discovered; if <code>true</code>,
* then those lookup services were just discarded.
*
* @serial
*/
protected final boolean discarded;
/**
* List consisting of marshalled proxy objects where each proxy
implements
* the <code>ServiceRegistrar</code> interface, and each is a proxy of
* one of the recently discovered or discarded lookup service(s); the
* lookup service(s) with which this event is associated.
* <p>
* Each proxy in this list is individually marshalled in order to add
* an additional 'layer' of serialization. Placing this serialization
* "wrapper" around each element prevents the deserialization mechanism
* from attempting to deserialize the individual elements in the list.
* That is, the deserialization mechanism will only deserialize the
list
* itself. After the list itself is successfully deserialized, the
client
* (or a third party, if the client requested that events be sent to a
* third party such as a mailbox), can then attempt to unmarshal each
* element separately. This allows each success to be captured, and
each
* failure to be noted.
* <p>
* If the elements of this list were not each marshalled separately,
* then upon encountering failure while attempting to deserialize any
* one element of the list, the deserialization mechanism's
* <code>readObject</code> method will throw an
<code>IOException</code>;
* resulting in the loss of all of the elements of the list, even those
* that could be successfully deserialized.
*
* @serial
*/
private final List<MarshalledObject> marshalledRegs;
/**
* Array containing a subset of the set of proxies to the lookup
* service(s) with which this event is associated. The elements of this
* array correspond to those elements of the <code>marshalledRegs<code>
* array that were successfully unmarshalled (at least once) as a
result
* of one or more invocations of the <code>getRegistrars</code> method
* of this event. Upon deserializing this event, this array is empty,
* but of the same size as <code>marshalledRegs<code>; and will be
* populated when the recipient of this event retrieves the registrars
* corresponding to the elements of <code>marshalledRegs<code>.
*
* @serial
*/
private final ServiceRegistrar[] regs;
/**
* <code>Map</code> from the service IDs of the registrars of this
event
* to the groups in which each registrar is a member.
*
* @serial
*/
private final Map<ServiceID,String> groups;
private static GetArg check(GetArg arg) throws IOException {
RemoteEvent sup = new RemoteEvent(arg);
if(sup.getSource() == null)
throw new
InvalidObjectException("RemoteDiscoveryEvent.readObject "
+"failure - source field is
null");
Collections.checkedList(
(List<MarshalledObject>) arg.get("marshalledRegs", null),
MarshalledObject.class
);
// Also handles null case.
if (!(arg.get("regs", null) instanceof ServiceRegistrar []))
throw new ClassCastException();
Collections.checkedMap(
(Map<ServiceID, String>) arg.get("groups", null),
ServiceID.class,
String.class
);
return arg;
}
public RemoteDiscoveryEvent(GetArg arg) throws IOException {
super(check(arg));
discarded = arg.get("discarded", false);
marshalledRegs = new ArrayList<MarshalledObject>(
(List<MarshalledObject>) arg.get("marshalledRegs", null));
regs = ((ServiceRegistrar[]) arg.get("regs", null)).clone();
// TreeMap to avoid hash collision attacks.
groups = new TreeMap<ServiceID, String>((Map<ServiceID, String>)
arg.get("groups", null));
integrity = MarshalledWrapper.integrityEnforced(arg);
}
/**
* Flag related to the verification of codebase integrity. A value of
* <code>true</code> in this field indicates that the last time this
* event was unmarshalled, the enforcement of codebase integrity was
* in effect.
*/
private transient boolean integrity;
/**
* Constructs a new instance of <code>RemoteDiscoveryEvent</code>.
*
* @param source reference to the lookup discovery service that
* generated the event
* @param eventID the event identifier that maps a particular
* registration to its listener and targeted groups
* and locators
* @param seqNum the sequence number of this event
* @param handback the client handback (null may be input)
* @param discarded flag indicating whether the event being
constructed
* is a discovery event or a discard event
* @param groups mapping from the registrars of this event to the
* groups in which each registrar is a member
*
* @throws java.io.IOException when serialization failure occurs on
* every registrar of this event. That is, if at least one
* registrar is successfully serialized, then this exception
* will not be thrown.
*
* @throws java.lang.NullPointerException this exception occurs when
* either <code>null</code> is input for the map parameter, or
* at least one element of that map is <code>null</code>.
*
* @throws java.lang.IllegalArgumentException this exception occurs
* when an empty set of registrars is input.
*/
public RemoteDiscoveryEvent(Object source,
long eventID,
long seqNum,
MarshalledObject handback,
boolean discarded,
Map<ServiceRegistrar,String> groups)
throws IOException
{
super(source, eventID, seqNum, handback);
this.discarded = discarded;
if(groups != null) {
/* If the set of registrars is empty, throw exception */
if(groups.size() == 0) {
throw new IllegalArgumentException("empty input map");
}
ServiceRegistrar[] registrars =
(ServiceRegistrar[])(groups.keySet()).toArray
(new
ServiceRegistrar[groups.size()]);
/* If any elements of the array are null, throw exception */
for(int i=0;i<registrars.length;i++) {
if(registrars[i] == null) {
throw new NullPointerException("null element ("+i
+") in input map");
}
}
/* Create a new marshalled instance of each element of the
* registrars array, and place each in the marshalledRegs
* ArrayList of this class. Also, construct the groups map that
* contains the mappings from the service ID of each registrar
* to the registrar's corresponding member groups.
*
* Drop any element that can't be serialized.
*/
this.groups = new HashMap<ServiceID,String>(groups.size());
this.marshalledRegs = new ArrayList(groups.size());
int l = registrars.length;
for(int i=0;i<l;i++) {
try {
marshalledRegs.add(new
MarshalledInstance(registrars[i]).convertToMarshalledObject());
(this.groups).put((registrars[i]).getServiceID(),
groups.get(registrars[i]) );
} catch(IOException e) { /* drop if can't serialize */ }
}
if( !(marshalledRegs.isEmpty()) ) {
regs = new ServiceRegistrar[marshalledRegs.size()];
} else {
throw new IOException("failed to serialize any of the "
+registrars.length+" elements");
}
} else {
throw new NullPointerException("null input map");
}
}//end constructor
/**
* Returns the value of the boolean flag that indicates whether this
* event is a discovery event or a discard event.
*
* @return <code>true</code> if this is a discard event,
<code>false</code>
* if it is a discovery event.
*/
public boolean isDiscarded() {
return discarded;
}//end isDiscarded
/**
* Returns an array consisting of instances of the ServiceRegistrar
* interface. Each element in the returned set is a proxy to one of
* the newly discovered or discarded lookup service(s) that caused
* the current instance of this event class to be sent to the listener
* of the client's registration. Note that a new array is returned
* on every call.
* <p>
* When the lookup discovery service sends an instance of this event
* class to the listener of a client's registration, the set of lookup
* service proxies contained in the event is sent as a set of
marshalled
* instances of the ServiceRegistrar interface. Thus, in order to
* construct the return set, this method attempts to unmarshal each
* element of that set of proxies. Should a failure occur while
* attempting to unmarshal any of the elements of the set of marshalled
* proxy objects contained in the current instance of this class, this
* method will throw an exception of type LookupUnmarshalException.
* <p>
* When a LookupUnmarshalException is thrown by this method, the
* contents of the exception provides the client with the following
* useful information: (1) the knowledge that a problem has occurred
* while unmarshalling at least one of the as yet unmarshalled proxy
* objects, (2) the set consisting of the proxy objects that were
* successfully unmarshalled (either on the current invocation of
* this method or on some previous invocation), (3) the set consisting
* of the marshalled proxy objects that could not be unmarshalled
* during the current or any previous invocation of this method, and
* (4) the set of exceptions corresponding to each failed attempt at
* unmarshalling during the current invocation of this method.
* <p>
* Typically, the type of exception that occurs when attempting to
* unmarshal an element of the set of marshalled proxies is either an
* IOException or a ClassNotFoundException. A ClassNotFoundException
* occurs whenever a remote field of the marshalled proxy cannot be
* retrieved (usually because the codebase of one of the field's
classes
* or interfaces is currently 'down'). To address this situation, the
* client may wish to invoke this method at some later time when the
* 'down' codebase(s) may be accessible. Thus, the client can invoke
* this method multiple times until all of the elements of the set of
* marshalled proxies can be successfully unmarshalled.
* <p>
* Note that once an element of the set of marshalled proxy objects has
* been successfully unmarshalled on a particular invocation of this
* method, the resulting unmarshalled proxy is stored for return on
* all future invocations of this method. That is, once successfully
* unmarshalled, no attempt will be made to unmarshal that element on
* any future invocations of this method. Thus, if this method returns
* successfully without throwing a LookupUnmarshalException, the client
* is guaranteed that all marshalled proxies have been successfully
* unmarshalled; and any future invocations of this method will return
* successfully.
*
* @return an array consisting of references to the discovered or
discarded
* lookup service(s) corresponding to this event.
*
* @throws net.jini.discovery.LookupUnmarshalException this exception
* occurs when at least one of the set of lookup service
* references cannot be deserialized (unmarshalled).
*
* @see net.jini.discovery.LookupUnmarshalException
*/
public ServiceRegistrar[] getRegistrars() throws
LookupUnmarshalException {
synchronized (marshalledRegs) {
if( marshalledRegs.size() > 0 ) {
List unmarshalledRegs = new ArrayList();
List exceptions = unmarshalRegistrars(marshalledRegs,
unmarshalledRegs);
/* Add the un-marshalled elements to the end of regs */
insertRegistrars(regs,unmarshalledRegs);
if( exceptions.size() > 0 ) {
throw(new LookupUnmarshalException
( clipNullsFromEnd(regs),
(MarshalledObject[])(marshalledRegs.toArray
(new
MarshalledObject[marshalledRegs.size()])),
(Throwable[])(exceptions.toArray
(new Throwable[exceptions.size()])),
"failed to unmarshal at least one
ServiceRegistrar") );
}//endif
}//endif
return clipNullsFromEnd(regs);
}//end sync(marshalledRegs)
}//end getRegistrars
/**
* Returns a set that maps to the service ID of each registrar
referenced
* by this event, the current set of groups in which each registrar
is a
* member.
* <p>
* To retrieve the set of member groups corresponding to any element
* of the array returned by the <code>getRegistrars</code> method,
* simply use the service ID of the desired element from that array as
* the key to the <code>get</code> method of the <code>Map</code>
object
* returned by this method and cast to <code>String</code>[].
* <p>
* Note that the same <code>Map</code> object is returned on every
* call to this method; that is, a copy is not made.
*
* @return <code>Map</code> whose key set consists of the service IDs
* of each lookup service with which this event is associated,
* and whose values are <code>String</code>[] arrays
containing
* the names of the groups in which the lookup service having
* the corresponding service ID is a member.
*/
public Map<ServiceID,String> getGroups() {
return new HashMap<ServiceID,String>(groups);
}//end getGroups
/**
* Attempts to unmarshal each element of the first input argument. When
* an element of that argument is successfully unmarshalled, that
element
* is removed from the first set and the resulting unmarshalled proxy
* is placed in the set referenced by the second input argument.
* Whenever failure occurs as a result of an attempt to unmarshal one
* of the elements of the first set, the exception that is thrown as
* as a result of that failure is placed in the returned set of
* exceptions, and the element from the first set that caused the
* failure is left in the first set to facilitate the creation of the
* <code>LookupUnmarshalException</code> that will ultimately be thrown
* from <code>getRegistrars</code>.
* <p>
* Note that there is a one-to-one correspondence between the
exceptions
* contained in the return set and the remaining elements in the first
* set after all unmarshalling attempts have completed.
*
* @param marshalledRegs an ArrayList object consisting of marshalled
* instances of ServiceRegistrar, each
* corresponding to a proxy to a lookup
service.
*
* @param unmarshalledRegs an ArrayList object consisting of all
* successfully unmarshalled proxies from
* the first argument.
*
* @return an ArrayList consisting of the exceptions that occur as a
* result of attempts to unmarshal each element of the first
* argument to this method.
*/
private List unmarshalRegistrars(List marshalledRegs,
List unmarshalledRegs)
{
ArrayList exceptions = new ArrayList();
/* Try to un-marshal each element in marshalledRegs; verify codebase
* when appropriate.
*
* If current element is successfully un-marshalled:
* -- record the un-marshalled element
* -- delete the corresponding marshalled element from its set
*
* If current element cannot be un-marshalled:
* -- place the exception in the return object
* -- leave the corresponding marshalled element in its set
* -- increment the index to the next marshalled element
*
* Note that index 'n' used in the loop is only a counter. That is,
* it is intentional that the element at index 'i' is the element
* that is unmarshalled, not the element at index 'n'. This is
because
* whenever the element is successfully unmarshalled, the element is
* removed from the set of marshalled registrars, decreasing
that set
* by 1 element. Thus, the 'next' element to unmarshal is
actually at
* the same index as the last element that was unmarshalled.
*/
int i = 0;
int nMarshalledRegs = marshalledRegs.size();
for(int n=0;n<nMarshalledRegs;n++) {
MarshalledObject marshalledObj
=
(MarshalledObject)(marshalledRegs.get(i));
MarshalledInstance marshalledInstance
= new MarshalledInstance(marshalledObj);
try {
ServiceRegistrar reg =
(ServiceRegistrar)(marshalledInstance.get(integrity));
/* Success: record the un-marshalled element
* delete the corresponding un-marshalled element
*/
unmarshalledRegs.add( reg );
marshalledRegs.remove(i);
} catch(Throwable e) {
exceptions.add(e);
i=i+1;
}
}//end loop
return exceptions;
}//end unmarshalRegistrars
/**
* Places the lookup service reference(s), contained in the input
* ArrayList, into the 'empty' slots occurring at the end (indicated
* by the first null element) of the input array.
*
* @param regsArray array that will receive the new references.
*
* @param regsList List containing the ServiceRegistrar references
* to place in regsArray input argument.
*/
private static void insertRegistrars(ServiceRegistrar[] regsArray,
List regsList)
{
if((regsArray != null) && (regsList != null)) {
int lenA = regsArray.length;
int lenB = regsList.size();
if((lenA == 0) || (lenB == 0)) return;
int beg = indexFirstNull(regsArray);
int end = ( (beg+lenB) <= lenA ? (beg+lenB) : (lenA) );
for(int i=beg, j=0; i<end; i++,j++) {
regsArray[i] = (ServiceRegistrar)(regsList.get(j));
}
}
}//end insertRegistrars
/**
* Convenience method that copies elements from the given array into
* a new array, and returns the new array. This method begins with the
* first element in the given array, and stops when it encounters the
* first <code>null</code> element.
*
* @param regsArray array from which to copy elements
*
* @return array of <code>ServiceRegistrar</code> containing each
element
* of the given array from its first element up to, but not
* including, the <code>null</code> element; and all subsequent
* elements. If the first element of the given array is
* <code>null</code>, then this method will return an empty array.
*/
private static ServiceRegistrar[] clipNullsFromEnd
(ServiceRegistrar[]
regsArray)
{
if( regsArray == null) return new ServiceRegistrar[0];
int clippedLen = indexFirstNull(regsArray);
ServiceRegistrar[] clippedArray = new ServiceRegistrar[clippedLen];
for(int i=0; i<clippedLen; i++) {
clippedArray[i] = regsArray[i];
}//end loop
return clippedArray;
}//end clipNullsFromEnd
/**
* Finds the index of the first element in the input array that
contains
* null.
* <p>
* If the array is null (or has zero length), -1 will be returned. If
* every element of the array is non-null, this method will return
* the length of the array. Thus, after invoking this method, it is
* important to test for these conditions to avoid the occurrence
of an
* IndexOutOfBoundsException when using the value returned by this
* method.
*
* @param arr Object array to examine for the first occurrence of null
*
* @return the index of the first element in the input array that
contains
* null. A value of -1 is returned if the input array is null;
* the length of the array is returned if no element in the
* array is null.
*/
private static int indexFirstNull(Object[] arr) {
int i = -1;
if( (arr == null) || (arr.length == 0) ) return i;
for(i=0;i<arr.length;i++) {
if(arr[i] == null) return i;
}
return i;
}//end indexFirstNull
/**
* When an instance of this class is deserialized, this method is
* automatically invoked. This implementation of this method validates
* the state of the deserialized instance, and additionally determines
* whether or not codebase integrity verification was performed when
* unmarshalling occurred.
*
* @throws <code>InvalidObjectException</code> if the state of the
* deserialized instance of this class is found to be invalid.
*/
private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException
{
s.defaultReadObject();
/* Verify source */
if(getSource() == null) {
throw new
InvalidObjectException("RemoteDiscoveryEvent.readObject "
+"failure - source field is
null");
}//endif
/* Retrieve the value of the integrity flag */
integrity = MarshalledWrapper.integrityEnforced(s);
}//end readObject
}//end class RemoteDiscoveryEvent
More information about the core-libs-dev
mailing list