/* * 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 com.sun.jini.reggie; import com.sun.jini.proxy.MarshalledWrapper; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.Serializable; import java.rmi.MarshalException; import java.rmi.UnmarshalException; import java.security.DigestOutputStream; import java.security.MessageDigest; import net.jini.loader.ClassLoading; /** * An EntryClass is a descriptor for an entry class, packaged up for * transmission between client-side proxies and the registrar server. * Instances are never visible to clients, they are private to the * communication between the proxies and the server. Note that we don't * transmit information about interfaces implemented by the class, because it * isn't necessary given the specific use of type information for entries. * <p> * This class only has a bare minimum of methods, to minimize * the amount of code downloaded into clients. * <p> * * @author Sun Microsystems, Inc. * * @see ClassMapper */ class EntryClass implements Serializable { private static final long serialVersionUID = 2L; /** * Class name * * @serial */ protected String name; /** * Hash for the type * * @serial */ protected long hash; /** * Descriptor for the superclass * * @serial */ protected EntryClass superclass; /** * Number of public fields * * @serial */ protected int numFields; /** Number of instances of this class in service registrations */ protected transient int numInstances; /** Number of templates of this class in event registrations */ protected transient int numTemplates; /** * An instance containing only name and hash, no superclass info. * This is only used on the registrar side, to minimize the amount * of info transmitted back to clients. */ protected transient EntryClass replacement; /** * Flag set to true if this instance was unmarshalled from an * integrity-protected stream, or false otherwise */ private transient boolean integrity = false; /** Should only be called by ClassMapper */ public EntryClass(Class clazz, EntryClass superclass) throws MarshalException { name = clazz.getName(); this.superclass = superclass; ClassMapper.EntryField[] fields = ClassMapper.getFields(clazz); numFields = fields.length; computeHash(fields); } /** * Constructor used for creating replacement instances, * containing only name and hash. */ private EntryClass(EntryClass orig) { name = orig.name; hash = orig.hash; } /** Return the superclass descriptor */ public EntryClass getSuperclass() { return superclass; } /** Return the number of public fields (including superclasses) */ public int getNumFields() { return numFields; } /** Set the number of instances of this class */ public void setNumInstances(int numInstances) { this.numInstances = numInstances; } /** Set the number of templates of this class */ public void setNumTemplates(int numTemplates) { this.numTemplates = numTemplates; } /** Return the replacement, if any, containing only name and rep. */ public synchronized EntryClass getReplacement() { if (replacement == null) replacement = new EntryClass(this); return replacement; } /** * This is really only needed in the registrar, but it's very * convenient to have here. * @see Class#isAssignableFrom */ public boolean isAssignableFrom(EntryClass cls) { for (EntryClass sup = cls; sup != null; sup = sup.superclass) { if (hash == sup.hash) return true; } return false; } /** * Returns the number of times this type is used in service * registrations * @return number of instances of this type in use in service * registrations */ public int getNumInstances() { return numInstances; } /** * Returns the number of times this type is used in event * registrations * @return number of times this type is used in event registrations */ public int getNumTemplates() { return numTemplates; } // Converts this type descriptor to a Class object public Class toClass(String codebase) throws IOException, ClassNotFoundException { Class cls = ClassLoading.loadClass(codebase, name, null, integrity, null); EntryClass local; try { local = ClassMapper.toEntryClassBase(cls).eclass; } catch (MarshalException e) { throw new UnmarshalException("problem obtaining local version of " + toString(), e); } if (hash != local.hash) throw new UnmarshalException("incoming entry type: " + toString() + " is not assignable to the local" + " version of the type: " + local); return cls; } /** * Returns the name of this type * @return the name of this type */ public String getName() { return name; } /** * Returns true if the object passed in is an instance of EntryClass * with the same type hash as this object. Returns false otherwise. * @param o object to compare this object against * @return true if this object equals the object passed in; false * otherwise. */ public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof EntryClass)) return false; EntryClass t = (EntryClass) o; return hash == t.hash; } /** * Return a hashcode for this type. * @return int the hashcode for this type */ public int hashCode() { return (int) (hash ^ (hash >>> 32)); } /* Inherit javadoc */ public String toString() { return getClass() + "[name=" + getName() + ", hash=" + hash + "]"; } /** * Computes a SHA-1 digest from the hash of the superclass, if there * is a superclass, followed by the name of this class, followed by * the name and type for each field, if any, declared by this class and * ordered alphabetically by field name. The first 8 bytes of the digest * are used to form the 64-bit hash value for this type. */ private void computeHash(ClassMapper.EntryField[] fields) throws MarshalException { hash = 0; try { MessageDigest md = MessageDigest.getInstance("SHA"); DataOutputStream out = new DataOutputStream( new DigestOutputStream(new ByteArrayOutputStream(127),md)); if (superclass != null) out.writeLong(superclass.hash); out.writeUTF(name); int startDeclaredFields = superclass != null ? superclass.numFields : 0; for (int i = startDeclaredFields; i < fields.length; i++) { out.writeUTF(fields[i].field.getName()); out.writeUTF(fields[i].field.getType().getName()); } out.flush(); byte[] digest = md.digest(); for (int i = Math.min(8, digest.length); --i >= 0; ) { hash += ((long) (digest[i] & 0xFF)) << (i * 8); } } catch (Exception e) { throw new MarshalException("Unable to calculate type hash for " + name, e); } } /** * Samples integrity protection setting (if any) of the stream from which * this instance is being deserialized and checks that valid values * for this object have been read from the stream. */ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); if (name == null) throw new InvalidObjectException("name cannot be null"); if (hash == 0) throw new InvalidObjectException("hash cannot be zero"); integrity = MarshalledWrapper.integrityEnforced(in); } /** * Throws InvalidObjectException, since data for this class is required. */ private void readObjectNoData() throws InvalidObjectException { throw new InvalidObjectException("no data"); } }