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}