/* * 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.io; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.ObjectStreamClass; import java.io.ObjectStreamException; import java.io.OutputStream; import java.io.Serializable; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import net.jini.io.context.IntegrityEnforcement; /* * Implementation note: This class uses the helper class * MarshalledObject that is in this package. To avoid confusion * with java.rmi.MarshalledObject the fully qualified class names * are used for both classes. */ /** * A <code>MarshalledInstance</code> contains an object in serialized * form. The contained object can be deserialized on demand when * explicitly requested. This allows an object to be sent from one VM * to another in a way that allows the receiver to control when and if * the object is deserialized. * <p> * The contained object is specified at construction time and can * either be provided in unserialized or serialized form. If provided * in unserialized form it will be serialized during construction * with the serialization semantics defined by * <code>MarshalOutputStream</code>. In particular, classes are annotated * with a codebase URL from which the class can be loaded (if available). * <p> * If the <code>MarshalledInstance</code> needs to deserialize the * contained object then the contained object will be deserialized with the * deserialization semantics defined by <code>MarshalInputStream</code>. * In particular, the codebase annotations associated with the contained * object may be used to load classes referenced by the contained object. * <p> * <code>MarshalledInstance</code> provides functionality similar to * <code>java.rmi.MarshalledObject</code>, but additionally provides * for the verification of codebase integrity. Unlike * <code>java.rmi.MarshalledObject</code>, it does not perform remote * object-to-stub replacement. * * @author Sun Microsystems, Inc. * @since 2.0 */ public class MarshalledInstance implements Serializable { /** * @serial Bytes of serialized representation. If <code>objBytes</code> is * <code>null</code> then the object marshalled was a <code>null</code> * reference. */ private byte[] objBytes = null; /** * @serial Bytes of location annotations, which are ignored by * <code>equals</code>. If <code>locBytes</code> is null, there were no * non-<code>null</code> annotations during marshalling. */ private byte[] locBytes = null; /** * @serial Stored hash code of contained object. * * @see #hashCode */ private int hash; static final long serialVersionUID = -5187033771082433496L; /** * Creates a new <code>MarshalledInstance</code> that contains the * marshalled representation of the current state of the supplied * object. The object is serialized with the semantics defined by * <code>MarshalOutputStream</code>. The output stream used to marshal the * object implements {@link ObjectStreamContext} and returns an empty * collection from its {@link ObjectStreamContext#getObjectStreamContext * getObjectStreamContext} method. * * @param obj The Object to be contained in the new * <code>MarshalledInstance</code> * @throws IOException if the object cannot be serialized */ public MarshalledInstance(Object obj) throws IOException { this(obj, Collections.EMPTY_SET); } /** * Creates a new <code>MarshalledInstance</code> that contains the * marshalled representation of the current state of the supplied * object. The object is serialized with the semantics defined by * <code>MarshalOutputStream</code>. The output stream used to marshal the * object implements {@link ObjectStreamContext} and returns the given * collection from its {@link ObjectStreamContext#getObjectStreamContext * getObjectStreamContext} method. * * @param obj The Object to be contained in the new * <code>MarshalledInstance</code> * @param context the collection of context information objects * @throws IOException if the object cannot be serialized * @throws NullPointerException if <code>context</code> is <code>null</code> */ public MarshalledInstance(Object obj, Collection context) throws IOException { if (context == null) throw new NullPointerException(); if (obj == null) { hash = 13; // null hash for java.rmi.MarshalledObject return; } ByteArrayOutputStream bout = new ByteArrayOutputStream(); ByteArrayOutputStream lout = new ByteArrayOutputStream(); MarshalledInstanceOutputStream out = new MarshalledInstanceOutputStream(bout, lout, context); out.writeObject(obj); out.flush(); objBytes = bout.toByteArray(); // locBytes is null if no annotations locBytes = (out.hadAnnotations() ? lout.toByteArray() : null); // Calculate hash from the marshalled representation of object // so the hashcode will be comparable when sent between VMs. // // Note: This calculation must match the calculation in // java.rmi.MarshalledObject since we use this hash // in the converted MarshalledObject. The reverse is // also true in that we use the MarshalledObject's // hash for our hash. (see the MarshalledInstance( // MarshalledObject) constructor) // int h = 0; for (int i = 0; i < objBytes.length; i++) { h = 31 * h + objBytes[i]; } hash = h; } /** * Creates a new <code>MarshalledInstance</code> from an * existing <code>MarshalledObject</code>. An object equivalent * to the object contained in the passed <code>MarshalledObject</code> * will be contained in the new <code>MarshalledInstance</code>. * <p> * The object contained in the passed <code>MarshalledObject</code> * will not be unmarshalled as part of this call. * * @param mo The <code>MarshalledObject</code> that contains * the object the new <code>MarshalledInstance</code> should * contain * @throws NullPointerException if <code>mo</code> is <code>null</code> */ public MarshalledInstance(java.rmi.MarshalledObject mo) { if (mo == null) throw new NullPointerException(); // To extract the java.rmi.MarshalledObject's fields we // convert the mo into a net.jini.io.MarshalledObject. // (See resolveClass() in FromMOInputStream) The private // version of MarshalledObject allows access to the needed // fields. // net.jini.io.MarshalledObject privateMO = null; try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(mo); oos.flush(); byte[] bytes = baos.toByteArray(); ByteArrayInputStream bais = new ByteArrayInputStream(bytes); ObjectInputStream ois = new FromMOInputStream(bais); privateMO = (net.jini.io.MarshalledObject)ois.readObject(); } catch (IOException ioe) { throw new AssertionError(ioe); } catch (ClassNotFoundException cnfe) { throw new AssertionError(cnfe); } objBytes = privateMO.objBytes; locBytes = privateMO.locBytes; hash = privateMO.hash; } /** * Creates a new <code>MarshalledObject</code> that will * contain an object equivalent to the object contained * in this <code>MarshalledInstance</code> object. * <p> * The object contained in this <code>MarshalledInstance</code> * object will not be unmarshalled as part of this call. * @return A new <code>MarshalledObject</code> which * contains an object equivalent to the object * contained in this <code>MarshalledInstance</code> */ public java.rmi.MarshalledObject convertToMarshalledObject() { // To create a java.rmi.MarshalledObject with previously // serialized data we first create a private // net.jini.io.MarshalledObject with the // data and then convert it to the final object by changing // the class during readObject(). (See resolveClass() in // ToMOInputStream) // net.jini.io.MarshalledObject privateMO = new net.jini.io.MarshalledObject(); privateMO.objBytes = objBytes; privateMO.locBytes = locBytes; privateMO.hash = hash; java.rmi.MarshalledObject mo = null; try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(privateMO); oos.flush(); byte[] bytes = baos.toByteArray(); ByteArrayInputStream bais = new ByteArrayInputStream(bytes); ObjectInputStream ois = new ToMOInputStream(bais); mo = (java.rmi.MarshalledObject)ois.readObject(); } catch (IOException ioe) { throw new AssertionError(ioe); } catch (ClassNotFoundException cnfe) { throw new AssertionError(cnfe); } return mo; } /** * Returns a new copy of the contained object. Deserialization is * performed with the semantics defined by <code>MarshalInputStream</code>. * The input stream used to unmarshal the object implements {@link * ObjectStreamContext} and returns a collection from its {@link * ObjectStreamContext#getObjectStreamContext getObjectStreamContext} * method which contains a single element of type {@link * IntegrityEnforcement}; the {@link IntegrityEnforcement#integrityEnforced * integrityEnforced} method of this element returns the specified * <code>verifyCodebaseIntegrity</code> value. * <p>MarshalledInstance</code> implements this method by calling * <code>{@link #get(ClassLoader, boolean, ClassLoader, Collection) * get}(null, verifyCodebaseIntegrity, null, null)</code>. * * @param verifyCodebaseIntegrity if <code>true</code> then * codebase integrity is verified, otherwise code base * integrity is not verified * @return a new copy of the contained object * @throws IOException if an * <code>IOException</code> occurs while deserializing the * object from its internal representation * @throws ClassNotFoundException if any classes necessary * for reconstructing the contained object can not * be found or if <code>verifyCodebaseIntegrity</code> * is <code>true</code> and the integrity of the * contained object's codebase cannot be confirmed */ public Object get(final boolean verifyCodebaseIntegrity) throws IOException, ClassNotFoundException { return get(null, verifyCodebaseIntegrity, null, null); } /** * Returns a new copy of the contained object. Deserialization is * performed with the semantics defined by <code>MarshalInputStream</code>. * If <code>context</code> is not <code>null</code> * the input stream used to unmarshal the object implements {@link * ObjectStreamContext} and returns the given collection from its {@link * ObjectStreamContext#getObjectStreamContext getObjectStreamContext} * method. * <p>If <code>context</code> is <code>null</code> * the input stream used to unmarshal the object implements {@link * ObjectStreamContext} and returns a collection from its {@link * ObjectStreamContext#getObjectStreamContext getObjectStreamContext} * method which contains a single element of type {@link * IntegrityEnforcement}; the {@link IntegrityEnforcement#integrityEnforced * integrityEnforced} method of this element returns the specified * <code>verifyCodebaseIntegrity</code> value. * * @param defaultLoader the class loader value (possibly * <code>null</code>) to pass as the <code>defaultLoader</code> * argument to <code>RMIClassLoader</code> methods * @param verifyCodebaseIntegrity if <code>true</code> then * codebase integrity is verified, otherwise code base * integrity is not verified * @param verifierLoader the class loader value (possibly * <code>null</code>) to pass to {@link * net.jini.security.Security#verifyCodebaseIntegrity * Security.verifyCodebaseIntegrity}, if * <code>verifyCodebaseIntegrity</code> is <code>true</code> * @param context the collection of context information objects or * <code>null</code> * @return a new copy of the contained object * @throws IOException if an * <code>IOException</code> occurs while deserializing the * object from its internal representation * @throws ClassNotFoundException if any classes necessary * for reconstructing the contained object can not * be found or if <code>verifyCodebaseIntegrity</code> * is <code>true</code> and the integrity of the * contained object's codebase cannot be confirmed */ public Object get(ClassLoader defaultLoader, final boolean verifyCodebaseIntegrity, ClassLoader verifierLoader, Collection context) throws IOException, ClassNotFoundException { if (objBytes == null) // must have been a null object return null; if (context == null) { context = Collections.singleton( new IntegrityEnforcement() { public boolean integrityEnforced() { return verifyCodebaseIntegrity; } } ); } ByteArrayInputStream bin = new ByteArrayInputStream(objBytes); // locBytes is null if no annotations ByteArrayInputStream lin = (locBytes == null ? null : new ByteArrayInputStream(locBytes)); MarshalledInstanceInputStream in = new MarshalledInstanceInputStream(bin, lin, defaultLoader, verifyCodebaseIntegrity, verifierLoader, context); in.useCodebaseAnnotations(); Object obj = in.readObject(); in.close(); return obj; } /** * Compares this <code>MarshalledInstance</code> to another * object. Returns true if and only if the argument refers to an instance * of <code>MarshalledInstance</code> that contains exactly the same * serialized form for its contained object as this object does and * has the same class codebase annotations. * * @param obj the object to compare with this * <code>MarshalledInstance</code> * @return <code>true</code> if the argument contains an object * with an equivalent serialized form and codebase; * otherwise returns <code>false</code> */ public boolean fullyEquals(Object obj) { if (equals(obj)) { MarshalledInstance other = (MarshalledInstance)obj; return Arrays.equals(locBytes, other.locBytes); } return false; } /** * Compares this <code>MarshalledInstance</code> to another * object. Returns true if and only if the argument refers to an instance * of <code>MarshalledInstance</code> that contains exactly the same * serialized form for its contained object as this object does. The * comparison ignores any class codebase annotations, so that * two objects can be equivalent if they have the same serialized * representation, except for the codebase of each class in the * serialized representation. * @param obj the object to compare with this * <code>MarshalledInstance</code> * @return <code>true</code> if the argument contains an object * with an equivalent serialized form; otherwise returns * <code>false</code> */ public boolean equals(Object obj) { if (obj == this) return true; if (obj instanceof MarshalledInstance) { MarshalledInstance other = (MarshalledInstance)obj; if (hash != other.hash) return false; return Arrays.equals(objBytes, other.objBytes); } return false; } /** * Returns the hash code for this <code>MarshalledInstance</code>. * The hash code is calculated only from the serialized form * of the contained object. * @return The hash code for this object */ public int hashCode() { return hash; } /** * Verify the case of null contained object. */ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); // If contained object is null, then hash and locBytes must be // proper // if ((objBytes == null) && ((hash != 13) || (locBytes != null))) throw new InvalidObjectException("Bad hash or annotation"); } /** * Protect against missing superclass. */ private void readObjectNoData() throws ObjectStreamException { throw new InvalidObjectException("Bad class hierarchy"); } /** * This class is used to marshal objects for * <code>MarshalledInstance</code>. It places the location annotations * to one side so that two <code>MarshalledInstance</code>s can be * compared for equality if they differ only in location * annotations. Objects written using this stream should be read back * from a <code>MarshalledInstanceInputStream</code>. * * @see MarshalledInstanceInputStream */ private static class MarshalledInstanceOutputStream extends MarshalOutputStream { /** The stream on which location objects are written. */ private ObjectOutputStream locOut; /** <code>true</code> if non-<code>null</code> annotations are * written. */ private boolean hadAnnotations; /** * Creates a new <code>MarshalledObjectOutputStream</code> whose * non-location bytes will be written to <code>objOut</code> and whose * location annotations (if any) will be written to * <code>locOut</code>. */ public MarshalledInstanceOutputStream(OutputStream objOut, OutputStream locOut, Collection context) throws IOException { super(objOut, context); this.locOut = new ObjectOutputStream(locOut); hadAnnotations = false; } /** * Returns <code>true</code> if any non-<code>null</code> location * annotations have been written to this stream. */ public boolean hadAnnotations() { return hadAnnotations; } /** * Overrides <code>MarshalOutputStream.writeAnnotation</code> * implementation to write annotations to the location stream. */ protected void writeAnnotation(String loc) throws IOException { hadAnnotations |= (loc != null); locOut.writeObject(loc); } public void flush() throws IOException { super.flush(); locOut.flush(); } } /** * The counterpart to <code>MarshalledInstanceOutputStream</code>. * * @see MarshalledInstanceOutputStream */ private static class MarshalledInstanceInputStream extends MarshalInputStream { /** * The stream from which annotations will be read. If this is * <code>null</code>, then all annotations were <code>null</code>. */ private ObjectInputStream locIn; /** * Creates a new <code>MarshalledObjectInputStream</code> that * reads its objects from <code>objIn</code> and annotations * from <code>locIn</code>. If <code>locIn</code> is * <code>null</code>, then all annotations will be * <code>null</code>. */ MarshalledInstanceInputStream(InputStream objIn, InputStream locIn, ClassLoader defaultLoader, boolean verifyCodebaseIntegrity, ClassLoader verifierLoader, Collection context) throws IOException { super(objIn, defaultLoader, verifyCodebaseIntegrity, verifierLoader, context); this.locIn = (locIn == null ? null : new ObjectInputStream(locIn)); } /** * Overrides <code>MarshalInputStream.readAnnotation</code> to * return locations from the stream we were given, or <code>null</code> * if we were given a <code>null</code> location stream. */ protected String readAnnotation() throws IOException, ClassNotFoundException { return (locIn == null ? null : (String)locIn.readObject()); } } /** * Input stream to convert <code>java.rmi.MarshalledObject</code> * into <code>net.jini.io.MarshalledObject</code>. */ private static class FromMOInputStream extends ObjectInputStream { public FromMOInputStream(InputStream in) throws IOException { super(in); } /** * Overrides <code>ObjectInputStream.resolveClass</code> to change * an occurence of class <code>java.rmi.MarshalledObject</code> to * class <code>net.jini.io.MarshalledObject</code>. */ protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { if (desc.getName().equals("java.rmi.MarshalledObject")) { return net.jini.io.MarshalledObject.class; } return super.resolveClass(desc); } } /** * Input stream to convert * <code>net.jini.io.MarshalledObject</code> into * <code>java.rmi.MarshalledObject</code>. */ private static class ToMOInputStream extends ObjectInputStream { public ToMOInputStream(InputStream in) throws IOException { super(in); } /** * Overrides <code>ObjectInputStream.resolveClass</code> * to change an occurence of class * <code>net.jini.io.MarshalledObject</code> * to class <code>java.rmi.MarshalledObject</code>. */ protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { if (desc.getName().equals("net.jini.io.MarshalledObject")) { return java.rmi.MarshalledObject.class; } return super.resolveClass(desc); } } }