Package com.data2semantics.yasgui.mgwtlinker.linker

Source Code of com.data2semantics.yasgui.mgwtlinker.linker.PermutationMapLinker

package com.data2semantics.yasgui.mgwtlinker.linker;

/*
* #%L
* YASGUI
* %%
* Copyright (C) 2013 Laurens Rietveld
* %%
* 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.
* #L%
*/

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;

import org.apache.commons.io.FileUtils;

import com.data2semantics.yasgui.mgwtlinker.server.BindingProperty;
import com.data2semantics.yasgui.shared.StaticConfig;
import com.google.gwt.core.ext.LinkerContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.TreeLogger.Type;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.linker.AbstractLinker;
import com.google.gwt.core.ext.linker.ArtifactSet;
import com.google.gwt.core.ext.linker.EmittedArtifact;
import com.google.gwt.core.ext.linker.LinkerOrder;
import com.google.gwt.core.ext.linker.Shardable;
import com.google.gwt.core.ext.linker.SyntheticArtifact;
import com.google.gwt.core.ext.linker.impl.SelectionInformation;

@LinkerOrder(LinkerOrder.Order.POST)
@Shardable
public class PermutationMapLinker extends AbstractLinker {

  public static final String EXTERNAL_FILES_CONFIGURATION_PROPERTY_NAME = "html5manifestlinker_files";
  public static final String PERMUTATION_MANIFEST_FILE_ENDING = ".appcache";
  public static final String PERMUTATION_FILE_ENDING = ".perm.xml";
  public static final String MANIFEST_MAP_FILE_NAME = "manifest.map";

  private XMLPermutationProvider xmlPermutationProvider;

  public PermutationMapLinker() {
    xmlPermutationProvider = new XMLPermutationProvider();
    manifestWriter = new ManifestWriter();
  }

  private ManifestWriter manifestWriter;

  @Override
  public String getDescription() {
    return "PermutationMapLinker";
  }

  @Override
  public ArtifactSet link(TreeLogger logger, LinkerContext context, ArtifactSet artifacts, boolean onePermutation) throws UnableToCompleteException {
    if (onePermutation) {
      Map<String, Set<BindingProperty>> permutationMap = buildPermutationMap(logger, context, artifacts);
      Set<Entry<String, Set<BindingProperty>>> entrySet = permutationMap.entrySet();

      // since we are in onePermutation there should be just one
      // strongName
      // better make sure..
      if (permutationMap.size() != 1) {
        logger.log(Type.ERROR, "There should be only one permutation right now, but there were: '" + permutationMap.size() + "'");
        throw new UnableToCompleteException();
      }

      Entry<String, Set<BindingProperty>> next = entrySet.iterator().next();
      String strongName = next.getKey();
      Set<BindingProperty> bindingProperties = next.getValue();

      // all artifacts for this compilation
      Set<String> artifactsForCompilation = getArtifactsForCompilation(logger, context, artifacts);

      ArtifactSet toReturn = new ArtifactSet(artifacts);
      PermutationArtifact permutationArtifact = new PermutationArtifact(PermutationMapLinker.class, strongName, artifactsForCompilation,
          bindingProperties);

      toReturn.add(permutationArtifact);
      return toReturn;
    }

    ArtifactSet toReturn = new ArtifactSet(artifacts);
    Map<String, Set<BindingProperty>> map = buildPermutationMap(logger, context, artifacts);

    if (map.size() == 0) {
      // hosted mode
      return toReturn;
    }

    Map<String, PermutationArtifact> permutationArtifactAsMap = getPermutationArtifactAsMap(artifacts);
   
    //we need different file sets/manifests for our dev version (unminimized js), and our stable version
    List<String> stableExternalFiles = getStableExternalFiles(logger, context);
    List<String> devExternalFiles = getDevExternalFiles(logger, context);
   

    // build manifest html page for our stable version (included as iframe in our webapp)
    String appcacheService = "manifest.appcache";
    String manifestHtmlPage = buildManifestHtmlPage(appcacheService);
    toReturn.add(emitString(logger, manifestHtmlPage, appcacheService + ".html"));
   
    // build manifest html page for our stable version (included as iframe in our webapp)
    String devManifestHtmlPage = buildManifestHtmlPage(appcacheService + "?type=dev");
    toReturn.add(emitString(logger, devManifestHtmlPage, "manifest.dev.appcache.html"));
   
    Set<String> allPermutationFiles = getAllPermutationFiles(permutationArtifactAsMap);

    // get all artifacts
    Set<String> allArtifacts = getArtifactsForCompilation(logger, context, artifacts);

    for (Entry<String, PermutationArtifact> entry : permutationArtifactAsMap.entrySet()) {
      PermutationArtifact permutationArtifact = entry.getValue();
      // make a copy of all artifacts
      HashSet<String> filesForCurrentPermutation = new HashSet<String>(allArtifacts);
      // remove all permutations
      filesForCurrentPermutation.removeAll(allPermutationFiles);
      // add files of the one permutation we are interested in
      // leaving the common stuff for all permutations in...
      filesForCurrentPermutation.addAll(entry.getValue().getPermutationFiles());
      filesForCurrentPermutation = appendVersionIfNeeded(filesForCurrentPermutation);
     
      String permXml = buildPermXml(logger, permutationArtifact, filesForCurrentPermutation, stableExternalFiles);

      // emit permutation information file
      SyntheticArtifact emitString = emitString(logger, permXml, permutationArtifact.getPermutationName() + PERMUTATION_FILE_ENDING);
      toReturn.add(emitString);
     
     
      // build manifest for our stable version
      String manifestFile = entry.getKey() + PERMUTATION_MANIFEST_FILE_ENDING;
      @SuppressWarnings("serial")
      Map<String, String> fallbacks = new HashMap<String, String>(){{put("/", "../index.jsp");}};
      String maniFest = buildManiFest(entry.getKey(), stableExternalFiles, filesForCurrentPermutation, fallbacks);
      toReturn.add(emitString(logger, maniFest, manifestFile));
     
      // build manifest for our dev version
      String devManifestFile = entry.getKey() + ".dev" + PERMUTATION_MANIFEST_FILE_ENDING;
      String devManiFest = buildManiFest(entry.getKey(), devExternalFiles, filesForCurrentPermutation);
      toReturn.add(emitString(logger, devManiFest, devManifestFile));

    }

    toReturn.add(createPermutationMap(logger, map));
    return toReturn;

  }

 
  private HashSet<String> appendVersionIfNeeded(HashSet<String> filesForCurrentPermutation) {
    HashSet<String> newFileSet = new HashSet<String>();
    for (String file: filesForCurrentPermutation) {
      if (file.contains("nocache")) {
        file += "?" + StaticConfig.VERSION;
      }
      newFileSet.add(file);
    }
    return newFileSet;
  }

  protected String buildPermXml(TreeLogger logger, PermutationArtifact permutationArtifact, Set<String> gwtCompiledFiles, List<String> otherResources)
      throws UnableToCompleteException {
    HashSet<String> namesForPermXml = new HashSet<String>(gwtCompiledFiles);
    namesForPermXml.addAll(otherResources);

    try {
      return xmlPermutationProvider.writePermutationInformation(permutationArtifact.getPermutationName(), permutationArtifact.getBindingProperties(),
          namesForPermXml);
    } catch (XMLPermutationProviderException e) {
      logger.log(Type.ERROR, "can not build xml for permutation file", e);
      throw new UnableToCompleteException();
    }

  }

  /**
   * @param permutationArtifactAsMap
   * @return
   */
  protected Set<String> getAllPermutationFiles(Map<String, PermutationArtifact> permutationArtifactAsMap) {
    Set<String> allPermutationFiles = new HashSet<String>();

    for (Entry<String, PermutationArtifact> entry : permutationArtifactAsMap.entrySet()) {
      allPermutationFiles.addAll(entry.getValue().getPermutationFiles());
    }
    return allPermutationFiles;
  }

  protected Map<String, PermutationArtifact> getPermutationArtifactAsMap(ArtifactSet artifacts) {
    Map<String, PermutationArtifact> hashMap = new HashMap<String, PermutationArtifact>();
    for (PermutationArtifact permutationArtifact : artifacts.find(PermutationArtifact.class)) {
      hashMap.put(permutationArtifact.getPermutationName(), permutationArtifact);
    }
    return hashMap;
  }

  protected boolean shouldArtifactBeInManifest(String pathName) {
    if (
        pathName.endsWith("symbolMap") ||
        pathName.endsWith(".xml.gz") ||
        pathName.endsWith("rpc.log") ||
        pathName.endsWith("gwt.rpc") ||
        pathName.endsWith("manifest.txt") ||
        pathName.startsWith("rpcPolicyManifest") ||
        pathName.startsWith("soycReport") ||
        pathName.endsWith(".cssmap") ||
        pathName.contains("sc/system") ||
        pathName.contains("sc/schema") ||
        pathName.contains("skins/Graphite") ||
        pathName.contains(" ")
      ) {
      return false;
    }
    return true;
  }

  protected Set<String> getArtifactsForCompilation(TreeLogger logger, LinkerContext context, ArtifactSet artifacts) {
    Set<String> artifactNames = new HashSet<String>();
    for (EmittedArtifact artifact : artifacts.find(EmittedArtifact.class)) {
      String pathName = artifact.getPartialPath();
      if (shouldArtifactBeInManifest(pathName)) {
        artifactNames.add(pathName);
      }
    }
    return artifactNames;

  }

  protected String buildManiFest(String moduleName, List<String> staticResources, Set<String> cacheResources) {
    return manifestWriter.writeManifest(staticResources, cacheResources, null);
  }
 
  protected String buildManiFest(String moduleName, List<String> staticResources, Set<String> cacheResources, Map<String, String> fallbacks) {
    return manifestWriter.writeManifest(staticResources, cacheResources, fallbacks);
  }

  protected String buildManifestHtmlPage(String manifestFileLocation) {
    String html = "<html manifest=\"" + manifestFileLocation + "\">\n";
    html += "<head>"
        + "<script type=\"text/javascript\">"
        + "var appCache = window.applicationCache;\n" +
        "\n" +
        "if (appCache != undefined && window.parent != undefined) {\n" +
        "  if (window.parent.appcacheEventCached != undefined) {\n" +
        "    // Fired after the first cache of the manifest.\n" +
        "    appCache.addEventListener('cached', window.parent.appcacheEventCached, false);\n" +
        "  }\n" +
        "  if (window.parent.appcacheEventChecking != undefined) {\n" +
        "    // Checking for an update. Always the first event fired in the sequence.\n" +
        "    appCache.addEventListener('checking', window.parent.appcacheEventCached, false);\n" +
        "  }\n" +
        "  if (window.parent.appcacheEventDownloading != undefined) {\n" +
        "    // An update was found. The browser is fetching resources.\n" +
        "    appCache.addEventListener('downloading', window.parent.appcacheEventDownloading, false);\n" +
        "  }\n" +
        "  if (window.parent.appcacheEventError != undefined) {\n" +
        "    // The manifest returns 404 or 410, the download failed,\n" +
        "    // or the manifest changed while the download was in progress.\n" +
        "    appCache.addEventListener('error', window.parent.appcacheEventError, false);\n" +
        "  }\n" +
        "  if (window.parent.appcacheEventNoupdate != undefined) {\n" +
        "    // Fired after the first download of the manifest.\n" +
        "    appCache.addEventListener('noupdate', window.parent.appcacheEventNoupdate, false);\n" +
        "  }\n" +
        "  if (window.parent.apcacheEventObsolete != undefined) {\n" +
        "    // Fired if the manifest file returns a 404 or 410.\n" +
        "    // This results in the application cache being deleted.\n" +
        "    appCache.addEventListener('obsolete', window.parent.apcacheEventObsolete, false);\n" +
        "  }\n" +
        "  if (window.parent.appcacheEventProgress != undefined) {\n" +
        "    // Fired for each resource listed in the manifest as it is being fetched.\n" +
        "    appCache.addEventListener('progress', window.parent.appcacheEventProgress, false);\n" +
        "  }\n" +
        "  if (window.parent.appcacheEventUpdateReady != undefined) {\n" +
        "    // Fired when the manifest resources have been newly redownloaded.\n" +
        "    appCache.addEventListener('updateready', window.parent.appcacheEventUpdateReady, false);\n" +
        "  }\n" +
        "}"
        + "</script>"
        + "</head>\n";
    html += "<body></body>\n";
    html += "</html>\n";
    return html;
  }
 
  private Set<String> getFontFiles() {
    HashSet<String> set = new HashSet<String>();
    set.add("../fonts/fonts.css?" + StaticConfig.VERSION);
    set.addAll(getExternalFilesFromDir("fonts", "", "eot", "svg", "ttf", "woff"));//don't append version string here
    return set;
  }

  /**
   * Retrieves files we should add to manifest, but which are not generated by GWT (i.e. images/js files we use separately)
   * @param logger
   * @param context
   * @return
   */
  protected List<String> getStableExternalFiles(TreeLogger logger, LinkerContext context) {
    ArrayList<String> set = new ArrayList<String>();
    //all external js/css files should be minimized/aggregated by our maven plugin
    set.add("../static/yasgui.js?" + StaticConfig.VERSION);
    set.add("../static/yasgui.css?" + StaticConfig.VERSION);
    set.add("../index.jsp");
    set.addAll(getFontFiles());
    set.addAll(getExternalFilesFromDir("images", "?" + StaticConfig.VERSION.replace(".", ""), "png", "jpg", "gif"));
    return set;
  }

  /**
   * Retrieves files we should add to manifest, but which are not generated by GWT (i.e. images/js files we use separately)
   * @param logger
   * @param context
   * @return
   */
  protected List<String> getDevExternalFiles(TreeLogger logger, LinkerContext context) {
    ArrayList<String> set = new ArrayList<String>(getExternalFilesFromDir("assets", "?" + StaticConfig.VERSION, "js", "css"));
    set.addAll(getExternalFilesFromDir("images", "?" + StaticConfig.VERSION.replace(".", ""), "png", "jpg", "gif"));
    set.addAll(getFontFiles());
    set.add("../dev.jsp");
    return set;
  }
 
  protected EmittedArtifact createPermutationMap(TreeLogger logger, Map<String, Set<BindingProperty>> map) throws UnableToCompleteException {

    try {
      String string = xmlPermutationProvider.serializeMap(map);
      return emitString(logger, string, MANIFEST_MAP_FILE_NAME);
    } catch (XMLPermutationProviderException e) {
      logger.log(Type.ERROR, "can not build manifest map", e);
      throw new UnableToCompleteException();
    }

  }

  protected Map<String, Set<BindingProperty>> buildPermutationMap(TreeLogger logger, LinkerContext context, ArtifactSet artifacts)
      throws UnableToCompleteException {

    HashMap<String, Set<BindingProperty>> map = new HashMap<String, Set<BindingProperty>>();

    for (SelectionInformation result : artifacts.find(SelectionInformation.class)) {
      Set<BindingProperty> list = new HashSet<BindingProperty>();
      map.put(result.getStrongName(), list);

      TreeMap<String, String> propMap = result.getPropMap();
      Set<Entry<String, String>> set = propMap.entrySet();

      for (Entry<String, String> entry : set) {
        BindingProperty bindingProperty = new BindingProperty(entry.getKey(), entry.getValue());
        list.add(bindingProperty);
      }

    }

    return map;

  }
  /**
   * I didnt find a proper way to get the relative location of our assets/images dir: are we in the project root, or are we in the target dir?
   * Therefore, use this
   * This depends on whether this project is deployed from eclipse, or via maven.
   * Therefore, this (ugly) workaround
   * @return
   */
  private String getWebappPath() {
    String webappPath = "";
    File file = new File("src/main/webapp");
    if (file.exists()) {
      webappPath = file.getPath() + "/";
    }
    return webappPath;
  }
 
  private Set<String> getExternalFilesFromDir(String dir, String append, String... includeExtensions) {
    HashSet<String> fileSet = new HashSet<String>();
    String webappPath = getWebappPath();
    @SuppressWarnings("unchecked")
    ArrayList<File> assetFiles = new ArrayList<File>(FileUtils.listFiles(new File(webappPath + dir), includeExtensions, true));
    for (File assetFile: assetFiles) {
      String manifestFile = assetFile.getPath();
      if (webappPath.length() > 0) {
        //we need to make the manifest files relative to webap dir
        manifestFile = manifestFile.substring(webappPath.length());
      }
      //the manifest file is located in the Yasgui subdir of our webapp. The files we are adding are in the parent folder:
      fileSet.add("../" + manifestFile + append);
    };
    return fileSet;
  }
}
TOP

Related Classes of com.data2semantics.yasgui.mgwtlinker.linker.PermutationMapLinker

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.