Package com.sun.sgs.impl.service.data

Source Code of com.sun.sgs.impl.service.data.ClassesTable$UpdateMapsResult

/*
* Copyright 2007-2009 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package com.sun.sgs.impl.service.data;

import com.sun.sgs.app.ManagedObject;
import com.sun.sgs.app.ObjectIOException;
import com.sun.sgs.impl.util.Int30;
import com.sun.sgs.service.Transaction;
import com.sun.sgs.service.store.ClassInfoNotFoundException;
import com.sun.sgs.service.store.DataStore;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.Serializable;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
* Manages information about class descriptors used to serialize managed
* objects.  This class caches information about class descriptors and class
* IDs that it obtains from the data store.  The cache is filled on demand, and
* uses soft and weak references, so it can be cleared by the GC as needed.
*/
final class ClassesTable {

    /*
     * TBD: Maybe use futures to avoid simultaneous requests for the same
     * class?  -tjb@sun.com (05/21/2007)
     */

    /** The data store used to store class information. */
    private final DataStore store;

    /*
     * Note that using concurrent maps turned out to be too inefficient to be
     * useful.  Because the maps are likely to quickly include all of the
     * entries needed for steady state operation, supporting fast concurrent
     * reads via a shared read lock seems like a better approach than
     * permitting concurrent modifications.  -tjb@sun.com (05/18/2007)
     */

    /**
     * Maps class IDs to class descriptors and associated information.  Use
     * soft references to the descriptors since they are still useful if
     * unreferenced, but can be reconstructed if needed.
     */
    private final Map<Integer, ClassDescInfo> classDescMap =
  new HashMap<Integer, ClassDescInfo>();

    /** Reference queue for cleared ObjectStreamClass soft references. */
    private final ReferenceQueue<ObjectStreamClass> refQueue =
  new ReferenceQueue<ObjectStreamClass>();

    /**
     * Maps class descriptors to class descriptors and associated information,
     * including class IDs.  Use weak references to the descriptors since they
     * are compared by identity, and so are not useful if no longer referenced.
     */
    private final Map<ObjectStreamClass, ClassDescInfo> classIdMap =
  new WeakHashMap<ObjectStreamClass, ClassDescInfo>();

    /** Lock this lock when accessing classDescMap and classIdMap. */
    private final ReadWriteLock lock = new ReentrantReadWriteLock();

    /**
     * A soft reference holder for class descriptor objects, and associated
     * information, stored in classDescMap.
     */
    private static class ClassDescInfo
  extends SoftReference<ObjectStreamClass>
    {
  /** The associated class ID key. */
  final int classId;

  /**
   * The name of the class, so that we can identify the class even if the
   * reference to the associated class descriptor has been cleared.
   */
  private final String className;

  /**
   * Whether the class is a ManagedObject class with a writeReplace
   * method.
   */
  private final boolean hasWriteReplace;

  /**
   * Whether the class is a ManagedObject class with a readResolve
   * method.
   */
  private final boolean hasReadResolve;

  /**
   * The name of the first non-serializable superclass if it lacks an
   * accessible no-argument constructor, or {@code null} if there is no
   * such class.  Note that it is illegal to deserialize an instance of a
   * class with such a superclass, but serialization does not enforce
   * that restriction.
   */
  private final String missingConstructorSuperclass;

  /** Creates an instance of this class. */
  ClassDescInfo(int classId,
          ObjectStreamClass classDesc,
          ReferenceQueue<ObjectStreamClass> queue)
  {
      super(classDesc, queue);
      this.classId = classId;
      Class<?> cl = classDesc.forClass();
      className = cl.getName();
      boolean isManagedObject = ManagedObject.class.isAssignableFrom(cl);
      hasWriteReplace =
    isManagedObject && hasSerializationMethod(cl, "writeReplace");
      hasReadResolve =
    isManagedObject && hasSerializationMethod(cl, "readResolve");
      missingConstructorSuperclass =
    computeMissingConstructorSuperclass(cl);
  }

  /**
   * Removes entries from the map that have been queued after being
   * garbage collected.
   */
  static void processQueue(ReferenceQueue<ObjectStreamClass> queue,
         Map<Integer, ?> map)
  {
      ClassDescInfo ref;
      /*
       * Reference queues don't provide a way to specify that the queue
       * contains a particular subclass of reference, so the unchecked
       * assignment can't be avoided.  -tjb@sun.com (05/18/2007)
       */
      while ((ref = (ClassDescInfo) (Object) queue.poll()) != null) {
    map.remove(ref.classId);
      }
  }

  /** Checks if the class can be instantiated. */
  void checkInstantiable() throws IOException {
      if (hasWriteReplace) {
    throw new IOException(
        "Managed objects must not define a Serialization " +
        "writeReplace method: " + className);
      } else if (hasReadResolve) {
    throw new IOException(
        "Managed objects must not define a Serialization " +
        "readResolve method: " + className);
      } else if (missingConstructorSuperclass != null) {
    throw new IOException(
        "Class " + className + " has a superclass without an" +
        " accessible no-argument constructor: " +
        missingConstructorSuperclass);
      }
  }
    }

    /** Creates an instance with the specified data store. */
    ClassesTable(DataStore store) {
  this.store = store;
    }

    /**
     * Returns an implementation of ClassSerialization that uses this table to
     * lookup class descriptors, uses the specified transaction when making
     * requests of the data store, and uses Int30 to represent class IDs.
     */
    ClassSerialization createClassSerialization(final Transaction txn) {
  return new ClassSerialization() {
      public void writeClassDescriptor(ObjectStreamClass classDesc,
               ObjectOutputStream out)
    throws IOException
      {
    Int30.write(getClassId(txn, classDesc), out);
      }
      public void checkInstantiable(ObjectStreamClass classDesc)
    throws IOException
      {
    getClassDescInfo(txn, classDesc).checkInstantiable();
      }
      public ObjectStreamClass readClassDescriptor(ObjectInputStream in)
    throws ClassNotFoundException, IOException
      {
    return getClassDesc(txn, Int30.read(in));
      }
  };
    }

    /**
     * Returns the class ID associated with a class descriptor.
     *
     * @param  txn the transaction under which the operation should take place
     * @param  classDesc the class descriptor
     * @return  the class ID
     * @throws  ObjectIOException if a problem occurs serializing the class
     *    descriptor
     * @throws  TransactionAbortedException if the data store was consulted and
     *    the transaction was aborted due to a lock conflict or timeout
     * @throws  TransactionNotActiveException if the data store was consulted
     *    and the transaction is not active
     * @throws  IllegalStateException if the data store was consulted and the
     *    operation failed because of a problem with the current
     *    transaction
     */
    int getClassId(Transaction txn, ObjectStreamClass classDesc) {
  return getClassDescInfo(txn, classDesc).classId;
    }

    /**
     * Returns the information associated with a class descriptor.
     * @param  txn the transaction under which the operation should take place
     * @param  classDesc the class descriptor
     * @return  the information about the class descriptor
     * @throws  ObjectIOException if a problem occurs serializing the class
     *    descriptor
     * @throws  TransactionAbortedException if the data store was consulted and
     *    the transaction was aborted due to a lock conflict or timeout
     * @throws  TransactionNotActiveException if the data store was consulted
     *    and the transaction is not active
     * @throws  IllegalStateException if the data store was consulted and the
     *    operation failed because of a problem with the current
     *    transaction
     */
    private ClassDescInfo getClassDescInfo(
  Transaction txn, ObjectStreamClass classDesc)
    {
  lock.readLock().lock();
  try {
      ClassDescInfo info = classIdMap.get(classDesc);
      if (info != null) {
    return info;
      }
  } finally {
      lock.readLock().unlock();
  }
  int classId = store.getClassId(txn, getClassInfo(classDesc));
  if (classId > Int30.MAX_VALUE) {
      throw new IllegalStateException(
    "Allocating more than " + Int30.MAX_VALUE +
    " classes is not supported");
  }
  return updateMaps(classId, classDesc).classDescInfo;
    }

    /**
     * Updates the maps to refer to the specified class ID and class
     * descriptor.  Returns the descriptor, and the associated information,
     * that ends up mapped to the class ID.  Note that the descriptor returned
     * may be different from the one passed in if another one was obtained
     * concurrently.
     */
    private UpdateMapsResult updateMaps(Integer classId,
          ObjectStreamClass classDesc)
    {
  lock.writeLock().lock();
  try {
      ClassDescInfo.processQueue(refQueue, classDescMap);
      ClassDescInfo info = classDescMap.get(classId);
      ObjectStreamClass existing = (info != null) ? info.get() : null;
      if (existing == null) {
    info = new ClassDescInfo(classId, classDesc, refQueue);
    classDescMap.put(classId, info);
      }
      if (!classIdMap.containsKey(classDesc)) {
    classIdMap.put(classDesc, info);
      }
      return new UpdateMapsResult(
    (existing != null) ? existing :  classDesc, info);
  } finally {
      lock.writeLock().unlock();
  }
    }

    /**
     * Stores the ObjectStreamClass and ClassDescInfo returned by a call to
     * updateMaps.  The return value needs to contain both to insure that it
     * maintains a hard reference to the ObjectStreamClass.
     */
    private static class UpdateMapsResult {
  final ObjectStreamClass classDesc;
  final ClassDescInfo classDescInfo;
  UpdateMapsResult(ObjectStreamClass classDesc,
       ClassDescInfo classDescInfo)
  {
      this.classDesc = classDesc;
      this.classDescInfo = classDescInfo;
  }
    }

    /**
     * Returns the class descriptor associated with a class ID.
     *
     * @param  txn the transaction under which the operation should take place
     * @param  classId the class ID
     * @return  the class descriptor
     * @throws  ObjectIOException if a problem occurs deserializing the class
     *    descriptor, including if the class ID is not found
     * @throws  TransactionAbortedException if the data store was consulted and
     *    the transaction was aborted due to a lock conflict or timeout
     * @throws  TransactionNotActiveException if the data store was consulted
     *    and the transaction is not active
     * @throws  IllegalStateException if the data store was consulted and the
     *    operation failed because of a problem with the current
     *    transaction
     */
    private ObjectStreamClass getClassDesc(Transaction txn, int classId) {
  lock.readLock().lock();
  try {
      SoftReference<ObjectStreamClass> ref = classDescMap.get(classId);
      if (ref != null) {
    ObjectStreamClass classDesc = ref.get();
    if (classDesc != null) {
        return classDesc;
    }
      }
  } finally {
      lock.readLock().unlock();
  }
  try {
      UpdateMapsResult result = updateMaps(
    classId, getClassDesc(store.getClassInfo(txn, classId)));
      return result.classDesc;
  } catch (ClassInfoNotFoundException e) {
      throw new ObjectIOException(
    "Problem deserializing class descriptor: " + e.getMessage(),
    e, false);
  }
    }

    /**
     * Converts a class descriptor into its serialized form.
     *
     * @param  classDesc the class descriptor
     * @return  the class information
     * @throws  ObjectIOException if a problem occurs serializing the class
     *    descriptor
     */
    private static byte[] getClassInfo(ObjectStreamClass classDesc) {
  ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
  ObjectOutputStream objectOut = null;
  try {
      objectOut = new ObjectOutputStream(byteOut);
      objectOut.writeObject(classDesc);
      objectOut.flush();
      return byteOut.toByteArray();
  } catch (IOException e) {
      throw new ObjectIOException(
    "Problem serializing class descriptor: " + e.getMessage(),
    e, false);
  } finally {
      if (objectOut != null) {
    try {
        objectOut.close();
    } catch (IOException e) {
    }
      }
  }
    }

    /**
     * Converts a class descriptor's serialized form into the descriptor.
     *
     * @param  classInfo the class information
     * @return  the class descriptor
     * @throws  ObjectIOException if a problem occurs deserializing the class
     *    descriptor
     */
    private static ObjectStreamClass getClassDesc(byte[] classInfo) {
  ObjectInputStream in = null;
  Exception exception;
  try {
      in = new ObjectInputStream(new ByteArrayInputStream(classInfo));
      ObjectStreamClass classDesc = (ObjectStreamClass) in.readObject();
      return classDesc;
  } catch (ClassNotFoundException e) {
      exception = e;
  } catch (IOException e) {
      exception = e;
  } finally {
      if (in != null) {
    try {
        in.close();
    } catch (IOException e) {
    }
      }
  }
  throw new ObjectIOException(
      "Problem obtaining class descriptor: " + exception.getMessage(),
      exception, false);
    }

    /**
     * Returns whether the class defines an inherited method used by
     * Serialization.
     */
    private static boolean hasSerializationMethod(
  Class<?> forClass, String methodName)
    {
  Method method = null;
  Class<?> cl = forClass;
  while (cl != null) {
      try {
    method = cl.getDeclaredMethod(methodName);
    break;
      } catch (NoSuchMethodException e) {
    cl = cl.getSuperclass();
      }
  }
  if (method == null || method.getReturnType() != Object.class) {
      return false;
  }
  int mods = method.getModifiers();
  if (Modifier.isStatic(mods) || Modifier.isAbstract(mods)) {
      return false;
  } else if (Modifier.isPublic(mods) || Modifier.isProtected(mods)) {
      return true;
  } else if (Modifier.isPrivate(mods)) {
      return forClass == cl;
  } else {
      return samePackage(forClass, cl);
  }
    }

    /** Checks if the two classes are in the same package. */
    private static boolean samePackage(Class<?> c1, Class<?> c2) {
  return c1.getClassLoader() == c2.getClassLoader() &&
      getPackageName(c1).equals(getPackageName(c2));
    }

    /** Returns the package name of the class. */
    private static String getPackageName(Class<?> cl) {
  String name = cl.getName();
  int pos = name.lastIndexOf('[');
  if (pos >= 0) {
      name = name.substring(pos + 2);
  }
  pos = name.lastIndexOf('.');
  return (pos < 0) ? "" : name.substring(0, pos);
    }

    /**
     * Returns the name of the first non-serializable superclass if it lacks an
     * accessible no-arguments constructor, else null.
     */
    private static String computeMissingConstructorSuperclass(Class<?> cl) {
  assert cl != null;
  Class<?> instantiatedClass = cl;
  while (Serializable.class.isAssignableFrom(cl)) {
      cl = cl.getSuperclass();
      if (cl == null) {
    return null;
      }
  }
  try {
      Constructor constructor = cl.getDeclaredConstructor();
      int modifiers = constructor.getModifiers();
      if (Modifier.isPublic(modifiers) ||
    Modifier.isProtected(modifiers) ||
    (!Modifier.isPrivate(modifiers) &&
     samePackage(cl, instantiatedClass)))
      {
    return null;
      } else {
    return cl.getName();
      }
  } catch (NoSuchMethodException e) {
      return cl.getName();
  }
    }     
}
TOP

Related Classes of com.sun.sgs.impl.service.data.ClassesTable$UpdateMapsResult

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.