Package org.dtk.resources.build

Source Code of org.dtk.resources.build.BuildRequest

package org.dtk.resources.build;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.dtk.resources.build.manager.BuildStatusManager;
import org.dtk.resources.exceptions.IncorrectParameterException;
import org.dtk.resources.packages.PackageRepository;
import org.dtk.util.JsonUtil;

/**
* Class holding full details of a build request. Also provides
* utility methods to transform build details into correct format
* to ease processing in Rhino-JS environment. All build requests
* are identified by a unique reference, calculated as a hash of
* variable parameters.
*
* @author James Thomas
*/

public class BuildRequest {
  /** User provided build details */
  List<Map<String, String>> packages;
  String cdn;
  String optimise;
  String cssOptimise;
  String platforms;
  String theme;
  List<Map<String, Object>> layers;
 
  /** Unique identifier for these build parameters, used to cache computed result. */
  String buildReference;
 
  /** Filename for the compressed build archive */
  protected static final String archivedBuildFile = "dojo.zip";
 
  /** Directory which contains the raw build output files */
  protected static final String buildArtifactsDir = "dojo";
 
  /** Formatter string for toString() output **/
  protected static final String format = "org.dtk.resources.build.BuildRequest: packages=%1$s " +
    "cdn=%2$s, optimise=%3$s, cssOptimise=%4$s, platforms=%5$s, themes=%6$s, layers=%7$s";
 
  /** Dojo build profile format */
  protected static final String profileFormat = "dependencies = %1$s; %2$s;";
 
  public static String transformJobsPaths;
 
  /** Empty theme identifier */
  protected static final String MISSING_THEME_NAME = "none";
 
  /**
   * Create a new build request from constructor parameters.
   *
   * @param packages - Modules reference these packages
   * @param cdn - Content Delivery Network
   * @param optimise - Optimisation level
   * @param layers - Build layers
   * @throws JsonParseException
   * @throws JsonMappingException
   * @throws NoSuchAlgorithmException
   * @throws IOException
   */
  public BuildRequest(List<Map<String, String>> packages, String cdn, String optimise, String cssOptimise, String platforms,
    String theme, List<Map<String, Object>> layers)
    throws JsonParseException, JsonMappingException, NoSuchAlgorithmException, IOException {
   
    this.packages = packages;
    this.cdn = cdn;
    this.optimise = optimise;
    this.cssOptimise = cssOptimise;
    this.platforms = platforms;
    this.theme = theme;
    this.layers = layers;
   
    // Generate unique build reference for this set of
    // parameters, used hash digest of parameters.
    this.buildReference = generateBuildDigest();
  }
 
  /**
   * Generate dojo build profile for this build request. Will contain
   * all the relevant parameters to pass to the build system.
   *
   * @return Dojo build profile for this request
   * @throws IOException - Unable to render build profile
   * @throws JsonMappingException - Unable to map from Java objects to JSON
   * @throws JsonParseException - Illegal JSON parsing error
   */
  public String getProfileText() throws JsonParseException, JsonMappingException, IOException {
    Map<String, Object> buildProfile = new HashMap<String, Object>();
   
    List<List<String>> modulePrefixes = getModulePrefixes();
   
    buildProfile.put("layers", getProfileLayers());
    buildProfile.put("layerOptimize", optimise);
       
    // REMOVE ME. Unclear how to force CSS files from themes to be included in the build output without
    // requiring a dijit module.
    // Also, CSS optimising is not complete in the new build system. If user has selected a theme,
    // all always run CSS compacting....
    if (!"none".equals(theme)) {
      buildProfile.put("cssOptimize", "on");
      buildProfile.put("theme", theme);
     
      // Need to force reference to dijit or the theme resources won't be
      // copied across.      
      if (!containsModulePrefix("dijit", modulePrefixes)) {       
        modulePrefixes.add(Arrays.asList("dijit", new File(getDojoLocation(), "dijit").getAbsolutePath()));
      }
    // DojoX modules often depend on dijit
    } else if (containsModulePrefix("dojox", modulePrefixes)) {
      modulePrefixes.add(Arrays.asList("dijit", new File(getDojoLocation(), "dijit").getAbsolutePath()))
    }
       
    buildProfile.put("prefixes", modulePrefixes);
   
    // Add build reference to the profile, this allows logging to flow back to Java land
    // from JavaScript execution.
    buildProfile.put("buildReference", buildReference);
   
    String transformJobs = FileUtils.readFileToString(new File(transformJobsPaths));   
    String profileText = String.format(profileFormat, JsonUtil.writeJavaToJson(buildProfile), transformJobs);
   
    return profileText;
  }
 
  protected boolean containsModulePrefix(String prefix, List<List<String>> modulePrefixes) {
    Iterator<List<String>> iter = modulePrefixes.iterator();
    while(iter.hasNext()) {
      List<String> prefixAndLocation = iter.next();
      // Format is ["prefix", "location"]
      if (prefix.equals(prefixAndLocation.get(0))) {
        return true;
      }
    }
   
    return false;
  }
 
  /**
   * Convert the module layers for this build request into the format
   * the dojo build system expects. This will be converted straight to JSON.
   *
   * @return Dojo build layers, using a map to mirror simple object format
   */
  protected List<Map<String, Object>> getProfileLayers() {
    List<Map<String, Object>> profileLayers = new ArrayList<Map<String, Object>>();
   
    Iterator<Map<String, Object>> layerIter = layers.iterator();
   
    while(layerIter.hasNext()) {
      final Map<String, Object> layer = layerIter.next();     
      final List<String> dependencies = new ArrayList<String>();
     
      // Generate dependencies list as just name property of each module
      Iterator<Map<String, String>> modulesIter = ((List<Map<String, String>>) layer.get("modules")).iterator();     
      while(modulesIter.hasNext()) {
        dependencies.add(modulesIter.next().get("name"));
      }
     
      if ("dojo.js".equals(layer.get("name"))) {
        dependencies.add("dojo.selector.acme");
      }
           
      // Create new layer objects in the map, just layer name
      // and module dependencies
      profileLayers.add(new HashMap<String, Object>() {{
        put("dependencies", dependencies);
        put("name", layer.get("name"));
      }});
    };
   
    return profileLayers;
  }
   
  /**
   * Generate the unique digest for this build request. Used to identify the same build job
   * between requests. Variable parameters used to control the build are hashed using the SHA-1
   * algorithm.
   *
   * @return Build digest reference
   * @throws JsonParseException - Error parsing layers to Json
   * @throws JsonMappingException - Error parsing layers to Json
   * @throws IOException - Error parsing layers to Json
   * @throws NoSuchAlgorithmException - Unable to access SHA-1 algorithm
   */
  protected String generateBuildDigest()
  throws JsonParseException, JsonMappingException, IOException, NoSuchAlgorithmException {
    // TODO: Sort module list, so that we don't compile same thing twice
    // when modules are included in a different order.

    // Convert host object to simple JSON representation. Simple
    // reliable text representation of java object state.
    String layersJson = JsonUtil.writeJavaToJson(layers),
      packagesJson = JsonUtil.writeJavaToJson(packages);

    // Get instance of hashing algorithm and update digest with
    // build parameters.
    MessageDigest md = MessageDigest.getInstance("SHA");
    md.update(layersJson.getBytes());
    md.update(packagesJson.getBytes());
    md.update(cdn.getBytes());
    md.update(theme.getBytes());
    md.update(optimise.getBytes());
    md.update(cssOptimise.getBytes());
    md.update(platforms.getBytes());

    // Generate BASE64 encoded result, replacing non-safe directory characters
    String optionsDigest = (new String( (new Base64()).encode(md.digest())));
    String digest = optionsDigest.replace('+', '~').replace('/', '_').replace('=', '_');

    return digest;
  }
 
  /**
   * Given a build reference, find the associated file path for result of
   * the build. Constructed from full build result cache directory and
   * unique build reference.
   *
   * @param buildDigest - Reference build identifier
   * @return String - Absolute file path for reference build id
   */
  public String getBuildResultPath() {
    // Generate the full file cache path from cache directory, build id and build result file
    File buildResultFile = new File(getBuildResultDir(), archivedBuildFile);
   
    return buildResultFile.getAbsolutePath();
  }
 
  public String getBuildResultArtifactsPath() {
    // Generate the full file cache path from cache directory, build id and build result file
    File buildResultFile = new File(getBuildResultDir(), buildArtifactsDir);
   
    return buildResultFile.getAbsolutePath();
  }
 
  /**
   * Return the directory path which will contain the build aritfacts for this
   * unique build request. Constructed from the build cache directory alongside
   * the custom build identifier.
   *
   * @return Directory containing build artifacts, archive, profile and files.
   */
  public String getBuildResultDir() {
    BuildStatusManager buildStatusManager = BuildStatusManager.getInstance();
    String buildCacheRepository = buildStatusManager.getBuildResultCachePath();
   
    // Add unique build identifier to the cache path location
    File buildResultDir = new File(buildCacheRepository, buildReference);
   
    return buildResultDir.getAbsolutePath();
  }
 
  /**
   * Does this build request include a valid theme? 
   *
   * @return Request has a theme
   */
  public Boolean hasTheme() {
    return !(MISSING_THEME_NAME.equals(theme));
  }
 
  public List<List<String>> getModulePrefixes() {
    PackageRepository packageRepo = PackageRepository.getInstance();
    List<List<String>> modulePrefixLocations = new ArrayList<List<String>>();
    Set<String> modulePrefixes = new HashSet<String>();
   
    // Create custom module lookup, used to match module prefixes with a
    // package location
    Map<String, String> packageLocationLookup = new HashMap<String, String>();   
    Iterator<Map<String, String>> iter = packages.iterator();   
    while(iter.hasNext()) {
      Map<String, String> referencedPackage = iter.next();
     
      String name = referencedPackage.get("name"),
        version = referencedPackage.get("version");
     
      packageLocationLookup.put(name, packageRepo.getPackageLocation(name, version));     
    }

    // Search through all module dependencies, creating location references for
    // all module prefixes
    Iterator<Map<String, Object>> layerIter = layers.iterator();
    while(layerIter.hasNext()) {
      Map<String, Object> layer = layerIter.next();
      List<Map<String, String>> layerModules = (List<Map<String, String>>) layer.get("modules");
      Iterator<Map<String, String>> modulesIter = layerModules.iterator();
      while (modulesIter.hasNext()) {
        Map<String, String> details = modulesIter.next();
        String moduleName = details.get("name");
        String modulePrefix = moduleName.split("\\.")[0];
        // If we haven't already resolved location for this prefix, ignoring DTK modules
        if (!modulePrefixes.contains(modulePrefix)) {
          String location = packageLocationLookup.get(details.get("package"));
          modulePrefixLocations.add(Arrays.asList(modulePrefix, (new File(location, modulePrefix)).getAbsolutePath()));
          modulePrefixes.add(modulePrefix);
        }
      }
    }
   
    return modulePrefixLocations;
  }
 

  /**
   * Return the unique build reference for this request, a digest
   * of the parameters.
   *
   * @return build reference
   */
  public String getBuildReference() {
    return buildReference;
  }
 
  /**
   * Return the location for the version of dojo reference by this
   * request.
   *
   * @return Location for reference dojo module
   */
  public String getDojoLocation() {
    PackageRepository packageRepo = PackageRepository.getInstance();
    return packageRepo.getPackageLocation("dojo", getDojoVersion());
  }
 
  /**
   * Get the version of Dojo referenced by this request
   *
   * @return Dojo version identifier
   */
  public String getDojoVersion() {
    return getDojoPackage().get("version");
  }
 
  /**
   * Return package details for Dojo version referenced by this
   * build request.
   *
   * @return Dojo package details
   */
  protected Map<String, String> getDojoPackage() {
    Iterator<Map<String, String>> iter = packages.iterator();
   
    while(iter.hasNext()) {
      Map<String, String> referencedPackage = iter.next();
      if ("dojo".equals(referencedPackage.get("name"))) {
        return referencedPackage;
      }
    }
   
    return null;
  }
 
  /**
   * Return human-readable string representation of this
   * object and its internal members.
   *
   * @throws IOException - Error mapping layers to JSON
   * @throws JsonMappingException - Error mapping layers to JSON
   * @throws JsonParseException - Error mapping layers to JSON
   */
  public String serialise() throws JsonParseException, JsonMappingException, IOException  {
    return String.format(format, JsonUtil.writeJavaToJson(packages), cdn, optimise, cssOptimise,
      platforms, theme, JsonUtil.writeJavaToJson(layers));
  }
}
TOP

Related Classes of org.dtk.resources.build.BuildRequest

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.