/* * Copyright 2011 Google Inc. * * 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 com.google.gwt.sample.core.linker; import com.google.gwt.core.ext.LinkerContext; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.core.ext.UnableToCompleteException; import com.google.gwt.core.ext.linker.AbstractLinker; import com.google.gwt.core.ext.linker.Artifact; 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.LinkerOrder.Order; import com.google.gwt.core.ext.linker.impl.SelectionInformation; import java.util.Arrays; import java.util.Date; /** * Linker for public path resources in the Application Cache. * <p> * <strong>Warning</strong>This linker sends all permutations to clients, * making it inappropriate for most production use.</strong> * <p> * To use: * <ol> * <li>Add {@code manifest="YOURMODULENAME/appcache.nocache.manifest"} to the * {@code <html>} tag in your base html file. E.g., * {@code <html manifest="mymodule/appcache.nocache.manifest">}</li> * <li>Add a mime-mapping to your web.xml file: * <p> * <pre>{@code <mime-mapping> * <extension>manifest</extension> * <mime-type>text/cache-manifest</mime-type> * </mime-mapping> * }</pre> * </li> * </ol> * <p> * On every compile, this linker will regenerate the appcache.nocache.manifest * file with files from the public path of your module. * <p> * To obtain a manifest that contains other files in addition to those * generated by this linker, create a class that inherits from this one * and overrides {@code otherCachedFiles()}, and use it as a linker instead: * <p> * <pre><blockquote> * {@code @Shardable} * public class MyAppCacheLinker extends AbstractAppCacheLinker { * {@code @Override} * protected String[] otherCachedFiles() { * return new String[] {"/MyApp.html","/MyApp.css"}; * } * } * </blockquote></pre> */ @LinkerOrder(Order.POST) public class SimpleAppCacheLinker extends AbstractLinker { private static final String MANIFEST = "appcache.nocache.manifest"; @Override public String getDescription() { return "AppCacheLinker"; } @Override public ArtifactSet link(TreeLogger logger, LinkerContext context, ArtifactSet artifacts, boolean onePermutation) throws UnableToCompleteException { ArtifactSet toReturn = new ArtifactSet(artifacts); if (onePermutation) { return toReturn; } if (toReturn.find(SelectionInformation.class).isEmpty()) { logger.log(TreeLogger.INFO, "DevMode warning: Clobbering " + MANIFEST + " to allow debugging. " + "Recompile before deploying your app!"); artifacts = null; } // Create the general cache-manifest resource for the landing page: toReturn.add(emitLandingPageCacheManifest(context, logger, artifacts)); return toReturn; } /** * Override this method to force the linker to also include more files * in the manifest. */ protected String[] otherCachedFiles() { return null; } /** * Creates the cache-manifest resource specific for the landing page. * * @param context the linker environment * @param logger the tree logger to record to * @param artifacts {@code null} to generate an empty cache manifest */ private Artifact<?> emitLandingPageCacheManifest(LinkerContext context, TreeLogger logger, ArtifactSet artifacts) throws UnableToCompleteException { StringBuilder publicSourcesSb = new StringBuilder(); StringBuilder staticResoucesSb = new StringBuilder(); if (artifacts != null) { // Iterate over all emitted artifacts, and collect all cacheable artifacts for (@SuppressWarnings("rawtypes") Artifact artifact : artifacts) { if (artifact instanceof EmittedArtifact) { EmittedArtifact ea = (EmittedArtifact) artifact; String pathName = ea.getPartialPath(); 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")) { // skip these resources } else { publicSourcesSb.append(pathName + "\n"); } } } String[] cacheExtraFiles = getCacheExtraFiles(); for (int i = 0; i < cacheExtraFiles.length; i++) { staticResoucesSb.append(cacheExtraFiles[i]); staticResoucesSb.append("\n"); } } // build cache list StringBuilder sb = new StringBuilder(); sb.append("CACHE MANIFEST\n"); sb.append("# Unique id #" + (new Date()).getTime() + "." + Math.random() + "\n"); // we have to generate this unique id because the resources can change but // the hashed cache.html files can remain the same. sb.append("# Note: must change this every time for cache to invalidate\n"); sb.append("\n"); sb.append("CACHE:\n"); sb.append("# Static app files\n"); sb.append(staticResoucesSb.toString()); sb.append("\n# Generated app files\n"); sb.append(publicSourcesSb.toString()); sb.append("\n\n"); sb.append("# All other resources require the user to be online.\n"); sb.append("NETWORK:\n"); sb.append("*\n"); logger.log(TreeLogger.INFO, "Be sure your landing page's <html> tag declares a manifest:" + " <html manifest=" + context.getModuleFunctionName() + "/" + MANIFEST + "\">"); // Create the manifest as a new artifact and return it: return emitString(logger, sb.toString(), MANIFEST); } /** * Obtains the extra files to include in the manifest. Ensures the returned * array is not null. */ private String[] getCacheExtraFiles() { String[] cacheExtraFiles = otherCachedFiles(); return cacheExtraFiles == null ? new String[0] : Arrays.copyOf(cacheExtraFiles, cacheExtraFiles.length); } }