Package ca.eandb.jdcp.server.classmanager

Source Code of ca.eandb.jdcp.server.classmanager.FileClassManager$FileChildClassManager

/*
* Copyright (c) 2008 Bradley W. Kimmel
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/

package ca.eandb.jdcp.server.classmanager;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import ca.eandb.util.UnexpectedException;
import ca.eandb.util.io.FileUtil;

/**
* A <code>ParentClassManager</code> that stores class definitions in a
* directory tree rooted at a provided location.
* @author Brad Kimmel
*/
public final class FileClassManager extends AbstractClassManager implements
    ParentClassManager {

  /** The extension to give to class definition files. */
  private static final String CLASS_EXTENSION = ".class";

  /** The extension to give to class digest files. */
  private static final String DIGEST_EXTENSION = ".md5";

  /** The digest algorithm to use. */
  private static final String DIGEST_ALGORITHM = "MD5";

  /** The directory in which to store the current class definitions. */
  private final File currentDirectory;

  /**
   * The directory in which to store class definitions for which there are
   * still existing child <code>ClassManager</code>s referencing those
   * classes, but for which the definition of the class was overwritten
   * after the child <code>ClassManager</code> was created.
   */
  private final File deprecatedDirectory;

  /**
   * The directory in which to store class definitions that apply only to
   * particular child <code>ClassManager</code>s.
   */
  private final File childrenDirectory;

  /** The index of the next child <code>ClassManager</code>. */
  private int nextChildIndex = 0;

  /**
   * A map keyed on class names.  Each class name is associated with a list
   * indicating the values of {@link #nextChildIndex} in effect at the points
   * when that class was redefined.  This allows child
   * <code>ClassManager</code>s to find old class definitions (specifically,
   * the one in effect when that child was created).
   */
  private final Map<String, List<Integer>> deprecationMap = new HashMap<String, List<Integer>>();

  /** A list of the child <code>ClassManager</code>s. */
  private final List<FileChildClassManager> activeChildren = new ArrayList<FileChildClassManager>();

  /**
   * A list of indices associated with released
   * <code>ChildClassManager</code>s, but whose corresponding snapshot
   * directories (under {@link #deprecatedDirectory}) have yet to be removed.
   * Snapshot directories may only be removed when all children with smaller
   * indices have been released.
   */
  private final List<Integer> deprecationPendingList = new ArrayList<Integer>();

  /**
   * Creates a new <code>FileClassManager</code>.
   * @param rootDirectory The working directory.
   * @throws IllegalArgumentException If <code>rootDirectory</code> does not
   *     refer to a directory.
   */
  public FileClassManager(String rootDirectory) throws IllegalArgumentException {
    this(new File(rootDirectory));
  }

  /**
   * Creates a new <code>FileClassManager</code>.
   * @param rootDirectory The working directory.
   * @throws IllegalArgumentException If <code>rootDirectory</code> does not
   *     refer to a directory.
   */
  public FileClassManager(File rootDirectory) throws IllegalArgumentException {
    if (!rootDirectory.isDirectory()) {
      throw new IllegalArgumentException("rootDirectory must be a directory");
    }
    this.currentDirectory = new File(rootDirectory, "current");
    this.deprecatedDirectory = new File(rootDirectory, "deprecated");
    this.childrenDirectory = new File(rootDirectory, "children");
    currentDirectory.mkdir();
    deprecatedDirectory.mkdir();
    childrenDirectory.mkdir();
    FileUtil.clearDirectory(deprecatedDirectory);
    FileUtil.clearDirectory(childrenDirectory);
  }

  private static final Comparator<? super Object> childComparator = new Comparator<Object>() {
    public int compare(Object a, Object b) {
      if (a instanceof FileChildClassManager) {
        return -((Integer) b).compareTo(((FileChildClassManager) a).childIndex);
      } else {
        return ((Integer) a).compareTo(((FileChildClassManager) b).childIndex);
      }
    }
  };

  /* (non-Javadoc)
   * @see ca.eandb.jdcp.server.classmanager.ParentClassManager#getChildClassManager(int)
   */
  public ca.eandb.jdcp.server.classmanager.ChildClassManager getChildClassManager(int id) {
    int index = Collections.binarySearch(activeChildren, id,
        childComparator);
    return (index >= 0) ? activeChildren.get(index) : null;
  }

  /**
   * Gets the relative path (without the extension) for files associated with
   * the given fully qualified class name.
   * @param className The fully qualified class name.
   * @return The relative path to a file associated with the class.
   */
  private String getBaseFileName(String className) {
    return className.replace('.', '/');
  }

  /**
   * Writes a class definition.
   * @param directory The directory under which to write the class
   *     definition.
   * @param name The fully qualified name of the class.
   * @param def A <code>ByteBuffer</code> containing the class definition.
   */
  private void writeClass(File directory, String name, ByteBuffer def) {
    writeClass(directory, name, def, computeClassDigest(def));
  }

  /**
   * Writes a class definition.
   * @param directory The directory under which to write the class
   *     definition.
   * @param name The fully qualified name of the class.
   * @param def A <code>ByteBuffer</code> containing the class definition.
   * @param digest The MD5 digest of the class definition.
   */
  private void writeClass(File directory, String name, ByteBuffer def, byte[] digest) {
    String baseName = getBaseFileName(name);
    File classFile = new File(directory, baseName + CLASS_EXTENSION);
    File digestFile = new File(directory, baseName + DIGEST_EXTENSION);

    try {
      FileUtil.setFileContents(classFile, def, true);
      FileUtil.setFileContents(digestFile, digest, true);
    } catch (IOException e) {
      e.printStackTrace();
      classFile.delete();
      digestFile.delete();
    }
  }

  /**
   * Moves a class definition from the specified directory tree to another
   * specified directory tree.
   * @param fromDirectory The root of the directory tree from which to move
   *     the class definition.
   * @param name The fully qualified name of the class to move.
   * @param toDirectory The root of the directory tree to move the class
   *     definition to.
   */
  private void moveClass(File fromDirectory, String name, File toDirectory) {
    String baseName = getBaseFileName(name);
    File fromClassFile = new File(fromDirectory, baseName + CLASS_EXTENSION);
    File toClassFile = new File(toDirectory, baseName + CLASS_EXTENSION);
    File fromDigestFile = new File(fromDirectory, baseName + DIGEST_EXTENSION);
    File toDigestFile = new File(toDirectory, baseName + DIGEST_EXTENSION);
    File toClassDirectory = toClassFile.getParentFile();

    toClassDirectory.mkdirs();
    fromClassFile.renameTo(toClassFile);
    fromDigestFile.renameTo(toDigestFile);
  }

  /**
   * Determines if the specified class exists in the specified directory
   * tree.
   * @param directory The root of the directory tree to examine.
   * @param name The fully qualified name of the class.
   * @return A value indicating if the class exists in the given directory
   *     tree.
   */
  private boolean classExists(File directory, String name) {
    String baseName = getBaseFileName(name);
    File classFile = new File(directory, baseName + CLASS_EXTENSION);
    File digestFile = new File(directory, baseName + DIGEST_EXTENSION);

    return classFile.isFile() && digestFile.isFile();
  }

  /**
   * Gets the contents of the specified file, or null if the file does not
   * exist.
   * @param file The <code>File</code> whose contents to obtain.
   * @return The file contents, or null if the file does not exist.
   */
  private byte[] getFileContents(File file) {
    if (file.exists()) {
      try {
        return FileUtil.getFileContents(file);
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
    return null;
  }

  /**
   * Gets the MD5 digest of a class definition.
   * @param directory The root of the directory tree containing the class.
   * @param name The fully qualified name of the class.
   * @return The MD5 digest of the class definition.
   */
  private byte[] getClassDigest(File directory, String name) {
    String baseName = getBaseFileName(name);
    File digestFile = new File(directory, baseName + DIGEST_EXTENSION);
    return getFileContents(digestFile);
  }

  /**
   * Gets the definition of a class.
   * @param directory The root of the directory tree containing the class
   *     definition.
   * @param name The fully qualified name of the class.
   * @return A <code>ByteBuffer</code> containing the class definition.
   */
  private ByteBuffer getClassDefinition(File directory, String name) {
    String baseName = getBaseFileName(name);
    File digestFile = new File(directory, baseName + CLASS_EXTENSION);
    return ByteBuffer.wrap(getFileContents(digestFile));
  }

  /**
   * Computes the MD5 digest of the given class definition.
   * @param def A <code>ByteBuffer</code> containing the class definition.
   * @return The MD5 digest of the class definition.
   */
  private byte[] computeClassDigest(ByteBuffer def) {
    try {
      MessageDigest alg = MessageDigest.getInstance(DIGEST_ALGORITHM);
      def.mark();
      alg.update(def);
      def.reset();
      return alg.digest();
    } catch (NoSuchAlgorithmException e) {
      throw new UnexpectedException(e);
    }
  }

  /* (non-Javadoc)
   * @see ca.eandb.jdcp.server.classmanager.ClassManager#getClassDigest(java.lang.String)
   */
  public byte[] getClassDigest(String name) {
    return getClassDigest(currentDirectory, name);
  }

  /* (non-Javadoc)
   * @see ca.eandb.jdcp.server.classmanager.ClassManager#setClassDefinition(java.lang.String, java.nio.ByteBuffer)
   */
  public void setClassDefinition(String name, ByteBuffer def) {
    byte[] digest = computeClassDigest(def);
    if (classExists(currentDirectory, name)) {
      byte[] oldDigest = getClassDigest(currentDirectory, name);
      if (Arrays.equals(digest, oldDigest)) {
        return;
      }
      if (nextChildIndex > 0) {
        File deprecatedDirectory = getDeprecatedDirectory(name, nextChildIndex);
        if (!classExists(deprecatedDirectory, name)) {
          moveClass(currentDirectory, name, deprecatedDirectory);
          List<Integer> deprecationList = deprecationMap.get(name);
          if (deprecationList == null) {
            deprecationList = new ArrayList<Integer>();
            deprecationMap.put(name, deprecationList);
          }
          deprecationList.add(nextChildIndex);
        }
      }
    }
    writeClass(currentDirectory, name, def, digest);
  }

  private File getDeprecatedDirectory(String name, int childIndex) {
    return new File(deprecatedDirectory, Integer.toString(childIndex));
  }

  /* (non-Javadoc)
   * @see ca.eandb.util.classloader.ClassLoaderStrategy#getClassDefinition(java.lang.String)
   */
  public ByteBuffer getClassDefinition(String name) {
    return getClassDefinition(currentDirectory, name);
  }

  /* (non-Javadoc)
   * @see ca.eandb.jdcp.server.classmanager.ParentClassManager#createChildClassManager()
   */
  public FileChildClassManager createChildClassManager() {
    FileChildClassManager child = new FileChildClassManager();
    activeChildren.add(child);
    return child;
  }

  /**
   * Releases the resources associated with a child
   * <code>ClassManager</code>.
   * @param child The child <code>ClassManager</code> to release.
   */
  private void releaseChildClassManager(FileChildClassManager child) {
    for (int i = 0; i < activeChildren.size(); i++) {
      FileChildClassManager current = activeChildren.get(i);
      if (current.childIndex == child.childIndex) {
        FileUtil.deleteRecursive(child.childDirectory);
        deprecationPendingList.add(child.childIndex);
        if (i == 0) {
          Collections.sort(deprecationPendingList);
          for (int pendingIndex : deprecationPendingList) {
            if (pendingIndex > current.childIndex) {
              break;
            }
            File pendingDirectory = new File(deprecatedDirectory, Integer.toString(pendingIndex + 1));
            FileUtil.deleteRecursive(pendingDirectory);
          }
        }
      }
    }
  }

  /**
   * A child <code>ClassManager</code> of a <code>FileClassManager</code>.
   * @author Brad Kimmel
   */
  private final class FileChildClassManager extends AbstractClassManager implements ca.eandb.jdcp.server.classmanager.ChildClassManager {

    /**
     * The root of the directory tree in which class definitions specific
     * to this <code>ChildClassManager</code> are stored.
     */
    private final File childDirectory;

    /** The index associated with this child. */
    private final int childIndex;

    /**
     * A value indicating whether this <code>ChildClassManager</code> has
     * been released.
     */
    private boolean released = false;

    /**
     * Creates a new <code>ChildClassManager</code>.
     */
    public FileChildClassManager() {
      this.childIndex = nextChildIndex++;
      this.childDirectory = new File(childrenDirectory, Integer
          .toString(childIndex));
    }

    /**
     * Ensures that this <code>ChildClassManager</code> has not been
     * released.
     * @throws IllegalStateException if this <code>ChildClassManager</code>
     *     has been released.
     */
    private void check() {
      if (released) {
        throw new IllegalStateException("Attempt to use a released child ClassManager.");
      }
    }

    /**
     * Gets the root of the directory tree in which the current definition
     * of the specified class associated with this
     * <code>ChildClassManager</code> is stored.
     * @param name The fully qualified name of the class.
     * @return The root of the directory in which to find the current
     *     definition of the class associated with this
     *     <code>ChildClassManager</code>.
     */
    private File getClassDirectory(String name) {
      check();
      if (classExists(childDirectory, name)) {
        return childDirectory;
      }

      List<Integer> deprecationList = deprecationMap.get(name);
      if (deprecationList != null) {
        int index = Collections.binarySearch(deprecationList, childIndex);
        index = Math.abs(index + 1);

        if (index < deprecationList.size()) {
          int deprecationIndex = deprecationList.get(index);
          File deprecatedDirectory = FileClassManager.this.getDeprecatedDirectory(name, deprecationIndex);
          if (!classExists(deprecatedDirectory, name)) {
            throw new UnexpectedException("Deprecated class missing.");
          }
          return deprecatedDirectory;
        }
      }

      return currentDirectory;
    }

    /* (non-Javadoc)
     * @see ca.eandb.jdcp.server.classmanager.ClassManager#getClassDigest(java.lang.String)
     */
    public byte[] getClassDigest(String name) {
      File directory = getClassDirectory(name);
      return FileClassManager.this.getClassDigest(directory, name);
    }

    /* (non-Javadoc)
     * @see ca.eandb.jdcp.server.classmanager.ClassManager#setClassDefinition(java.lang.String, java.nio.ByteBuffer)
     */
    public void setClassDefinition(String name, ByteBuffer def) {
      check();
      writeClass(childDirectory, name, def);
    }

    /* (non-Javadoc)
     * @see ca.eandb.util.classloader.ClassLoaderStrategy#getClassDefinition(java.lang.String)
     */
    public ByteBuffer getClassDefinition(String name) {
      File directory = getClassDirectory(name);
      return FileClassManager.this.getClassDefinition(directory, name);
    }

    /**
     * Gets this <code>ChildClassManager</code>s parent
     * <code>FileClassManager</code>.
     * @return The <code>FileClassManager</code> that created this
     *     <code>ChildClassManager</code>.
     */
    public FileClassManager getParent() {
      return FileClassManager.this;
    }

    /* (non-Javadoc)
     * @see ca.eandb.jdcp.server.classmanager.ChildClassManager#release()
     */
    public void release() {
      released = true;
      releaseChildClassManager(this);
    }

    /* (non-Javadoc)
     * @see ca.eandb.jdcp.server.classmanager.ChildClassManager#getChildId()
     */
    public int getChildId() {
      check();
      return childIndex;
    }

  }

}
TOP

Related Classes of ca.eandb.jdcp.server.classmanager.FileClassManager$FileChildClassManager

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.