001package org.gwtbootstrap3.extras.cachemanifest; 002 003/* 004 * #%L 005 * GwtBootstrap3 006 * %% 007 * Copyright (C) 2013 GwtBootstrap3 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023import com.google.gwt.core.ext.LinkerContext; 024import com.google.gwt.core.ext.TreeLogger; 025import com.google.gwt.core.ext.UnableToCompleteException; 026import com.google.gwt.core.ext.linker.*; 027import com.google.gwt.core.ext.linker.EmittedArtifact.Visibility; 028import com.google.gwt.core.ext.linker.LinkerOrder.Order; 029 030import java.util.Date; 031import java.util.HashSet; 032import java.util.Set; 033import java.util.SortedSet; 034 035/** 036 * Offline linker performs the task of generating a valid cache manifest file 037 * when you compile your GWT application. 038 * <p/> 039 * <p/> 040 * Static resources that are needed (outside of the compile unit) require 041 * specific inclusion. These files would typically be index.html, css files or 042 * any resources not included within the GWT application. These files are 043 * included through the cachemanifest_static_files property added to your 044 * module.gwt.xml file. The path is relative to manifest, so include a full path 045 * if you include resources outside of the apps path. 046 * <p/> 047 * <pre> 048 * {@code 049 * <extend-configuration-property name="cachemanifest_static_files" value="/index.html" /> 050 * } 051 * </pre> 052 * <p/> 053 * To activate the linker, the following configuration is included in your GWT 054 * module definition (module.gwt.xml file) as follows: 055 * <p/> 056 * <pre> 057 * {@code 058 * <inherits name='org.gwtbootstrap3.extras.cachemanifest.Offline'/> 059 * <add-linker name="offline" /> 060 * } 061 * </pre> 062 * <p/> 063 * Finally, include the cache manifest file within the html page that loads your 064 * GWT application, as follows: 065 * <p/> 066 * <pre> 067 * {@code 068 * <!doctype html> 069 * <html manifest="<modulename>/appcache.manifest"> 070 * .... 071 * </html> 072 * } 073 * </pre> 074 * 075 * @author Grant Slender 076 */ 077 078@LinkerOrder(Order.POST) 079public class Offline extends AbstractLinker { 080 081 private static final String CACHEMANIFEST_STATIC_FILES_PROPERTY = "cachemanifest_static_files"; 082 083 @Override 084 public String getDescription() { 085 return "Offline Linker"; 086 } 087 088 @Override 089 public ArtifactSet link(final TreeLogger logger, final LinkerContext context, final ArtifactSet artifacts) throws UnableToCompleteException { 090 091 final ArtifactSet artifactset = new ArtifactSet(artifacts); 092 093 final HashSet<String> resources = new HashSet<String>(); 094 for (final EmittedArtifact emitted : artifacts.find(EmittedArtifact.class)) { 095 096 if (skipArtifact(emitted)) 097 continue; 098 resources.add(emitted.getPartialPath()); 099 } 100 101 final SortedSet<ConfigurationProperty> staticFileProperties = context.getConfigurationProperties(); 102 for (final ConfigurationProperty configurationProperty : staticFileProperties) { 103 final String name = configurationProperty.getName(); 104 if (CACHEMANIFEST_STATIC_FILES_PROPERTY.equals(name)) { 105 for (final String value : configurationProperty.getValues()) { 106 resources.add(value); 107 } 108 } 109 } 110 111 final String manifestString = buildManifestContents(resources); 112 if (manifestString != null) { 113 final EmittedArtifact manifest = emitString(logger, manifestString, "appcache.manifest"); 114 artifactset.add(manifest); 115 } 116 return artifactset; 117 } 118 119 private boolean skipArtifact(final EmittedArtifact emitted) { 120 121 if (emitted.getVisibility().matches(Visibility.Private)) 122 return true; 123 124 final String pathName = emitted.getPartialPath(); 125 126 if (pathName.endsWith("symbolMap")) 127 return true; 128 if (pathName.endsWith(".xml.gz")) 129 return true; 130 if (pathName.endsWith("rpc.log")) 131 return true; 132 if (pathName.endsWith("gwt.rpc")) 133 return true; 134 if (pathName.endsWith("manifest.txt")) 135 return true; 136 if (pathName.startsWith("rpcPolicyManifest")) 137 return true; 138 if (pathName.startsWith("soycReport")) 139 return true; 140 if (pathName.endsWith(".cssmap")) 141 return true; 142 143 return false; 144 } 145 146 private String buildManifestContents(final Set<String> resources) { 147 if (resources == null) 148 return null; 149 150 final StringBuilder sb = new StringBuilder(); 151 sb.append("CACHE MANIFEST\n"); 152 sb.append("# Version: " + (new Date()).getTime() + "." + Math.random() + "\n"); 153 sb.append("\n"); 154 sb.append("CACHE:\n"); 155 for (final String resourcePath : resources) { 156 sb.append(resourcePath + "\n"); 157 } 158 159 sb.append("\n\n"); 160 sb.append("NETWORK:\n"); 161 sb.append("*\n"); 162 return sb.toString(); 163 } 164}