Package org.nebulaframework.core.job.archive

Source Code of org.nebulaframework.core.job.archive.GridArchive

/*
* Copyright (C) 2008 Yohan Liyanage.
*
* Licensed 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 org.nebulaframework.core.job.archive;

import java.io.Externalizable;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.Serializable;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nebulaframework.core.job.GridJob;
import org.nebulaframework.deployment.classloading.GridArchiveClassLoader;
import org.nebulaframework.util.hashing.SHA1Generator;
import org.nebulaframework.util.io.IOSupport;
import org.springframework.util.Assert;

/**
* Represents an archived {@code GridJob}. An archived {@code GridJob} is a
* special format of {@code JAR}, which is referred to as
* {@code Nebula Archive}, identified by the extension {@code .nar}.
* <p>
* Nebula Archives allow required libraries ({@code .jar}) to be included
* within the archive itself, unlike the standard {@code JAR} file format.
* <p>
* The structure of Nebula Archive is as follows: <code>
<pre>
*   META-INF/
*           |
*           - Manifest.mf
*           - ...
*   NEBULA-INF/
*             |
*             - lib /
*                   |
*                   - library1.jar
*                   - ...
*             - ...
*   yourpackage /
*               |
*               - ...
*   your.class
*   ...
* </pre>
</code> The libraries are to be packaged in {@code NEBULA-INF/lib} directory.
* A special class loader is used by Nebula Framework to load required classes
* from the libraries included in a Nebula Archive. Refer to
* {@link GridArchiveClassLoader} for additional information regarding class
* loading.
* <p>
* {@code GridArchive} keeps the {@code byte[]} of a {@code .nar} file, and SHA1
* Hash for the {@code byte[]}, for verification purposes. The hash for the
* {@code byte[]} is generated at the time of creation of the
* {@code GridArchive} instance for a {@code .nar} file. At each remote node,
* this hash will be compared with a SHA1-Hash generated at the time, to ensure
* that the {@code GridArchive} contains valid data.
* <p>
* To instantiate a {@code GridArchive}, use the following factory methods
* <ul>
* <li>{@link #fromFile(File)}</li>
* </ul>
* <p>
* This class implements {@link Externalizable} interface, instead of
* {@link Serializable} to improve performance in communications, by reducing
* the data transfer amount and serialization time [Grosso, W. 2001. "Java RMI",
* Section 10.7.1].
*
* @author Yohan Liyanage
* @version 1.0
*
* @see GridJob
* @see GridArchiveClassLoader
*/
public class GridArchive implements Serializable {

  private static final long serialVersionUID = -5657326124238562531L;

  private static Log log = LogFactory.getLog(GridArchive.class);

  /**
   * Default directory name for {@code NEBULA-INF} inside a Nebula Archive
   * file.
   */
  public static final String NEBULA_INF = "NEBULA-INF";

  /**
   * Default path for JAR Libraries inside a Nebula Archive file.
   */
  public static final String LIBRARY_PATH = NEBULA_INF + "/lib";

  private String[] jobClassNames; // Class Names of GridJobs in .nar
  private byte[] bytes; // bytes of .nar file
  private String hash; // SHA1 Hash for bytes

  /**
   * Constructs a {@code GridArchive} with given bytes of {@code .nar} file,
   * and the names of {@code GridJob} classes.
   * <p>
   * SHA-1 Hash for the given {@code byte[]} will be calculated during the
   * instantiation process.
   * <p>
   * Note that the constructor is of <b>{@code protected}</b> scope. To
   * instantiate this type, use the factory method {@link #fromFile(File)}.
   *
   * @param bytes
   *            {@code byte[]} of {@code .nar} file
   * @param jobClassNames
   *            {@code String[]} of fully qualified class names of
   *            {@code GridJob} classes inside the .nar file.
   *
   * @see #fromFile(File)
   */
  protected GridArchive(byte[] bytes, String[] jobClassNames) {
    super();

    // Assertions
    Assert.notNull(bytes);
    Assert.notNull(jobClassNames);

    this.bytes = bytes;
    this.jobClassNames = jobClassNames;

    // Generate SHA1 Hash for bytes
    hash = SHA1Generator.generateAsString(bytes);
  }

  /**
   * Returns the bytes of the {@code .nar} file, represented by this
   * {@code GridArchive} instance.
   * <p>
   * This is a clone of internal byte[]. Changes to the return value will not
   * be reflected by this GridArchive.
   *
   * @return bytes of {@code .nar} file
   */
  public byte[] getBytes() {
    // Return a clone to protect internal state
    return bytes.clone();
  }

  /**
   * Returns the SHA-1 Hash generated at the time of creation of this
   * {@code GridArchive}, for the bytes of {@code .nar} file.
   *
   * @return SHA-1 Hash as {@code String}
   */
  public String getHash() {
    return hash;
  }

  /**
   * Returns an array of {@code String}s, which contains fully qualified
   * class names of {@code GridJob}s inside the {@code .nar} file.
   * <p>
   * Note that this method returns a clone of the intenal object, and any
   * changes to the return value will not be reflected in this GridArchive
   * instance.
   *
   * @return Class names of {@code GridJob}s inside {@code .nar} file
   */
  public String[] getJobClassNames() {
    return jobClassNames.clone();
  }

  /**
   * <b>Factory Method</b> to create a {@code GridArchive} instance for the
   * given {@code File} instance of a {@code .nar} file.
   *
   * @param file
   *            {@code File} instance of {@code .nar} file.
   *
   * @return {@code GridArchive} instance for given {@code .nar} file.
   *
   * @throws GridArchiveException
   *             if processing of {@code File} failed.
   */
  public static GridArchive fromFile(File file) throws GridArchiveException {
    try {
      // Assertions
      Assert.notNull(file);

      // Verify file integrity
      if (!verify(file)) {
        throw new SecurityException(
            "Grid Archive Verification failed of " + file);
      }

      // Detect the GridJob Class names
      String[] jobClassNames = findJobClassNames(file);

      // Read byte[] from File
      byte[] bytes = IOSupport.readBytes(new FileInputStream(file));

      // Create and return GridArchive
      return new GridArchive(bytes, jobClassNames);

    } catch (Exception e) {
      throw new GridArchiveException("Cannot create Grid Archive", e);
    }
  }

  /**
   * Verifies the integrity of the given {@code File}, as a Nebula Archive.
   *
   * @param file
   *            {@code File} to be verified.
   * @return if success, {@code true}, otherwise {@code false}.
   */
  protected static boolean verify(File file) {
    // FIXME Implement to verify the NAR
    return file.exists();
  }

  /**
   * Returns the {@code GridJob} classes with in the given {@code .nar} file.
   * Uses {@link GridArchiveClassLoader}.
   *
   * @param file
   *            {@code File} instance for {@code .nar} file.
   *
   * @return Fully qualified class names of {@code GridJob} classes in the
   *         file.
   *
   * @throws IOException
   *             if occurred during File I/O operations
   *
   * @see GridArchiveClassLoader
   */
  protected static String[] findJobClassNames(final File file)
      throws IOException {

    // Instantiate ClassLoader for given File
    ClassLoader classLoader = AccessController
        .doPrivileged(new PrivilegedAction<ClassLoader>() {
          public GridArchiveClassLoader run() {
            return new GridArchiveClassLoader(file);
          }

        });

    // Find ClassNames of all classes inside the file (except in NEBULA-INF)
    // Content inside .jar files will not be processed
    String[] allClassNames = getAllClassNames(file);

    // Holds Class<?> instances loaded by ClassLoader, for all classes
    List<String> jobClassNames = new ArrayList<String>();

    for (String className : allClassNames) {
      try {
        // Load each Class and check if its a GridJob Class
        if (isGridJobClass(classLoader.loadClass(className))) {
          jobClassNames.add(className);
        }
      } catch (ClassNotFoundException e) {
        // Log and continue with rest
        log.debug("[GridArchive] Unable to load class " + className);
      }
    }
    return jobClassNames.toArray(new String[] {});
  }

  /**
   * Detects all classes inside the given {@code .nar} file and returns an
   * array of fully qualified class name of each class, as {@code String}.
   *
   * @param file
   *            {@code .nar File}
   *
   * @return Fully qualified class names classes in {@code File}
   *
   * @throws IOException
   *             if occurred during File I/O operations
   */
  protected static String[] getAllClassNames(File file) throws IOException {

    // Holds Class Names
    List<String> names = new ArrayList<String>();

    // Create ZipArchive for File
    ZipFile archive = new ZipFile(file);
    Enumeration<? extends ZipEntry> entries = archive.entries();

    // Read each entry in archive
    while (entries.hasMoreElements()) {

      ZipEntry entry = entries.nextElement();

      // Ignore Directories
      if (entry.isDirectory())
        continue;

      // Ignore content in NEBULA-INF
      if (entry.getName().startsWith(GridArchive.NEBULA_INF)) {
        continue;
      }

      // Add each file which is a valid class file to list
      if (isClass(entry.getName())) {
        names.add(toClassName(entry.getName()));
      }
    }
    return names.toArray(new String[] {});
  }

  /**
   * Detects whether a given {@code Class} implements
   * {@code GridJob interface}, using Reflection API.
   *
   * @param cls
   *            {@code Class} to be checked
   * @return if {@code GridJob} class, {@code true}, otherwise {@code false}
   */
  protected static boolean isGridJobClass(Class<?> cls) {

    // Get all interfaces, and process each
    for (Class<?> iface : cls.getInterfaces()) {
      // If class implements GridJob interfaces
      if (isGridJobInterface(iface)) {
        log.debug("[GridArchive] Found GridJob Class " + cls.getName());
        return true;
      }
    }
    return false;
  }

  /**
   * Returns true if the given interface is a sub-interface of {@code GridJob}
   * marker interface.
   *
   * @param intrface
   *            interface to check
   * @return if {@code GridJob}, {@code true}, otherwise {@code false}
   */
  private static boolean isGridJobInterface(Class<?> intrface) {
    for (Class<?> iface : intrface.getInterfaces()) {
      if (iface.getName().equals(GridJob.class.getName())) {
        return true;
      }
    }
    return false;
  }

  /**
   * Converts the given file name to fully qualified class name. For example,
   * for '{@code org/nebulaframework/Grid.class}', this method returns '{@code org.nebulaframework.Grid}'.
   *
   * @param fileName
   *            File name to be converted
   * @return Fully qualified Class Name
   */
  protected static String toClassName(String fileName) {
    String name = fileName.substring(0, fileName.length()
        - ".class".length());
    return name.replaceAll("\\/|\\\\", "."); // Replace all path
                          // separators (Win/Linux)
  }

  /**
   * Returns {@code true} if the given file name (path) identifies a class
   * file. The identification is done by checking if the file name ends with '{@code .class}'.
   *
   * @param fileName
   *            File Name to be checked
   * @return if class, {@code true}, otherwise {@code false}
   */
  protected static boolean isClass(String fileName) {
    return fileName.endsWith(".class");
  }

}
TOP

Related Classes of org.nebulaframework.core.job.archive.GridArchive

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.