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    }