001 package 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
023 import com.google.gwt.core.ext.LinkerContext;
024 import com.google.gwt.core.ext.TreeLogger;
025 import com.google.gwt.core.ext.UnableToCompleteException;
026 import com.google.gwt.core.ext.linker.*;
027 import com.google.gwt.core.ext.linker.EmittedArtifact.Visibility;
028 import com.google.gwt.core.ext.linker.LinkerOrder.Order;
029
030 import java.util.Date;
031 import java.util.HashSet;
032 import java.util.Set;
033 import 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)
079 public 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 }