HTML5 Offline Web-Application

566 views
Skip to first unread message

StrongSteve

unread,
Aug 24, 2011, 4:19:10 AM8/24/11
to Google Web Toolkit
Hello,

I already succeeded in tweaking the simpleappcachelinker class a
little bit to create a first simple Offline application.

Now I have two question concerning this topic:

1.) Are there methods inside GWT to read HTML5 Offline Web Appliation
events? (Online? Offline? Finished Caching? Caching not
necessary? ...)

2.) appcache.nocache.manifest contains all HTML files generated by
GWT. The effect is that a user calling the web app from FireFox will
cache all HTML files inside this browser. Altough he/she would only
need the FireFox HTML file. Difficult topic because creating the
manifest happens during linking, browser detection during runtime.
Any thoughts on this?

Thanks for your help!

Greetings
Stefan

Shawn Brown

unread,
Aug 24, 2011, 6:31:57 AM8/24/11
to google-we...@googlegroups.com
Hi,

> I already succeeded in tweaking the simpleappcachelinker class a
> little bit to create a first simple Offline application.
>
> Now I have two question concerning this topic:
>
> 1.) Are there methods inside GWT to read HTML5 Offline Web Appliation
> events? (Online? Offline? Finished Caching? Caching not
> necessary? ...)

I found the Devloper Tools in Chrome to be extremely useful here. It
shows the events and what is in the cache.

this was useful to validate the manifest http://manifest-validator.com/

> 2.) appcache.nocache.manifest contains all HTML files generated by
> GWT. The effect is that a user calling the web app from FireFox will
> cache all HTML files inside this browser. Altough he/she  would only
> need the FireFox HTML file. Difficult topic because creating the
> manifest happens during linking, browser detection during runtime.

It's really easier than you think.

The link method gets called for each permutation, so just set it up
make a different manifest for each permutation.

Then on your server when your application.manifest is requested,
detect the useragent and version and serve back the appropriate file
for that permutation. With serlvet filters this is pretty easy.

There is also a linker on the web someplace, can't find the link just
now, that makes a property file with the userAgent and hash (directory
containing that permutation). I don't really use it but just glanced
at it to veryfy things were right. The only trick was from within
each link call figuring out what permutation is being created.

I have this;

Properties props = new Properties();

for (CompilationResult result : artifacts.find(CompilationResult.class)) {
permutation = result.getStrongName();

SortedSet<SortedMap<SelectionProperty, String>> propertiesMap =
result.getPropertyMap();
for (SortedMap<SelectionProperty, String> sm : propertiesMap) {
for (Map.Entry<SelectionProperty, String> e : sm.entrySet()) {
selectionProperty = e.getKey();
if ("locale".equals(selectionProperty.getName())) {
locale = e.getValue();
}
if ("user.agent".equals(selectionProperty.getName())) {
userAgent = e.getValue();
}
}
}
props.setProperty(userAgent + "." + locale, permutation);
}

Shawn

StrongSteve

unread,
Aug 25, 2011, 3:56:17 AM8/25/11
to Google Web Toolkit
Hello Shwan,

Thanks for you reply!

@1: Chrome Dev Tools are great. Nevertheless I was thinking about
reading the online/offline DOM flag from within my client code to
detect whether the application is online or offline.

@2: Great idea. Servlet filter, intercepting requests to
appcache.nocache.manifest and returning the correct one (depending on
browser version) seems pretty clear to me.

But I am really stuck on how to write the linker class that generates
different appcache.nocache.manifest files for every permutation.
I am using the class SimpleAppCacheLinker (see below) provided within
a GWT mobile sample.
This linker get called once during the GWT compile - right after all
permutations have been compiled. And not, as you said once for every
permutation. GWT API says that the link() method gets called once -
and only once - too.

Can you point me in the right direction on how to generated manifests
for different permutations?
Maybe even a code snippet?

THANKS!

Greetings
Stefan

<code>
package com.siemens.html5.whiteboard.linker;

/*
* 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.
*/
import java.util.Arrays;
import java.util.Date;
import java.util.Map;
import java.util.SortedMap;
import java.util.SortedSet;

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.CompilationResult;
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.SelectionProperty;
import com.google.gwt.core.ext.linker.impl.SelectionInformation;
import com.google.gwt.dev.cfg.Properties;

/**
* Currently taken from the Google SVN. Should be part of GWT 2.4 <br>
*
* AppCacheLinker - linker for public path resources in the
Application Cache.
* <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();
System.out.println("Pathname: " +pathName);
if (pathName.endsWith("symbolMap")
|| pathName.endsWith(".xml.gz")
|| pathName.endsWith("rpc.log")
|| pathName.endsWith("gwt.rpc")
|| pathName.endsWith("manifest.txt")
|| pathName.startsWith("rpcPolicyManifest")) {
// 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);
}
}

</code>

On 24 Aug., 12:31, Shawn Brown <big.coffee.lo...@gmail.com> wrote:
> Hi,
>
> > I already succeeded in tweaking the simpleappcachelinker class a
> > little bit to create a first simple Offline application.
>
> > Now I have two question concerning this topic:
>
> > 1.) Are there methods inside GWT to read HTML5 Offline Web Appliation
> > events? (Online? Offline? Finished Caching? Caching not
> > necessary? ...)
>
> I found the Devloper Tools in Chrome to be extremely useful here.  It
> shows the events and what is in the cache.
>
> this was useful to validate the manifesthttp://manifest-validator.com/

Shawn Brown

unread,
Aug 25, 2011, 4:19:53 AM8/25/11
to google-we...@googlegroups.com
> @1: Chrome Dev Tools are great. Nevertheless I was thinking about
> reading the online/offline DOM flag from within my client code to
> detect whether the application is online or offline.

Oh, that I do not know.

> @2: Great idea. Servlet filter, intercepting requests to
> appcache.nocache.manifest and returning the correct one (depending on
> browser version) seems pretty clear to me.
>
> But I am really stuck on how to write the linker class that generates
> different appcache.nocache.manifest files for every permutation.

oops sorry forgot to say use @Shardable

@Shardable @LinkerOrder(LinkerOrder.Order.POST) public class
SimpleAppCacheLinker extends AbstractLinker {

see http://google-web-toolkit.googlecode.com/svn/javadoc/2.1/com/google/gwt/core/ext/linker/Shardable.html

be sure to read the api for the method you'll use to understand the
behavior - it calls link for every permutation and then one final time
(with onePermutation set as true )

StrongSteve

unread,
Aug 25, 2011, 5:55:59 AM8/25/11
to Google Web Toolkit
Sorry for getting confused with the API. I did not want to sound
"rude"...

Using the @Shardable annotation, the method link(...) gets called for
every permutation.
The paramter ArtifactSet artifacts contains exactly those files I need
and want to add to my offline manifest.

Sadly I am stuck on getting the user.agent of the permuation, the
method link has been called for.
Using the LinkerContext returns null for the property user.agent.

The only solution I have come up so far is this:
(It generates an offline manifest for every user.agent. So after
compiling I have six different manifests in the WAR directory. I plan
on writing a servlet filter that returns the correct one upon
requesting the offline manifest...)

@Override
public ArtifactSet link(TreeLogger logger, LinkerContext context,
ArtifactSet artifacts,
boolean onePermutation)
throws UnableToCompleteException {

ArtifactSet toReturn = new ArtifactSet(artifacts);

for (CompilationResult compilationResult :
artifacts.find(CompilationResult.class)) {
for (SelectionProperty property :
compilationResult.getPropertyMap().first().keySet()) {
if (property.getName().equals("user.agent")) {
String userAgent = compilationResult.getPropertyMap()
.first().get(property);
System.out.println(userAgent);
// Create the general cache-manifest resource for the
// landing page:
toReturn.add(emitLandingPageCacheManifest(context, logger,
artifacts, userAgent));
}
}
}

This calls a method for generating a offline manifest for every
user.agent. But there gotta be an easier way than to navigate over the
compilation result?


Any ideas!

Thanks!
Stefan

On 25 Aug., 10:19, Shawn Brown <big.coffee.lo...@gmail.com> wrote:
> > @1: Chrome Dev Tools are great. Nevertheless I was thinking about
> > reading the online/offline DOM flag from within my client code to
> > detect whether the application is online or offline.
>
> Oh, that I do not know.
>
> > @2: Great idea. Servlet filter, intercepting requests to
> >appcache.nocache.manifest and returning the correct one (depending on
> > browser version) seems pretty clear to me.
>
> > But I am really stuck on how to write the linker class that generates
> > differentappcache.nocache.manifest files for every permutation.
>
> oops sorry forgot to say use @Shardable
>
> @Shardable @LinkerOrder(LinkerOrder.Order.POST) public class
> SimpleAppCacheLinker extends AbstractLinker {
>
> seehttp://google-web-toolkit.googlecode.com/svn/javadoc/2.1/com/google/g...

Shawn Brown

unread,
Aug 25, 2011, 6:36:24 AM8/25/11
to google-we...@googlegroups.com
Hi,

> Sorry for getting confused with the API. I did not want to sound
> "rude"...

Yeah no problem at all...

> The only solution I have come up so far is this:
> (It generates an offline manifest for every user.agent. So after
> compiling I have six different manifests in the WAR directory.

Which is what you want right?

> @Override
>  public ArtifactSet link(TreeLogger logger, LinkerContext context,
> ArtifactSet artifacts,
>      boolean onePermutation)
>      throws UnableToCompleteException {
>
>    ArtifactSet toReturn = new ArtifactSet(artifacts);
>
>        for (CompilationResult compilationResult :
> artifacts.find(CompilationResult.class)) {
>                for (SelectionProperty property :
> compilationResult.getPropertyMap().first().keySet()) {
>                        if (property.getName().equals("user.agent")) {
>                                String userAgent = compilationResult.getPropertyMap()
>                                                .first().get(property);
>                                System.out.println(userAgent);
>                                // Create the general cache-manifest resource for the
>                                // landing page:
>                                toReturn.add(emitLandingPageCacheManifest(context, logger,
>                                                artifacts, userAgent));
>                        }
>                }
>        }
>
> This calls a method for generating a offline manifest for every
> user.agent. But there gotta be an easier way than to navigate over the
> compilation result?

I don't know one. I need to add in some .css and .gif files, as well
as get the rpc stuff under network so I do the following: (well I
took some other stuff out ... but this is mostly it )
. Your code is probably better than mine.

@Override public ArtifactSet link(TreeLogger logger, LinkerContext
context, ArtifactSet artifacts, boolean onePermutation)
throws UnableToCompleteException {

String permutation = null;
String locale = "";
String userAgent = "";
SelectionProperty selectionProperty = null;


Properties props = new Properties();

for (CompilationResult result : artifacts.find(CompilationResult.class)) {
permutation = result.getStrongName();

SortedSet<SortedMap<SelectionProperty, String>> propertiesMap =
result.getPropertyMap();
for (SortedMap<SelectionProperty, String> sm : propertiesMap) {
for (Map.Entry<SelectionProperty, String> e : sm.entrySet()) {
selectionProperty = e.getKey();
if ("locale".equals(selectionProperty.getName())) {
locale = e.getValue();
}
if ("user.agent".equals(selectionProperty.getName())) {
userAgent = e.getValue();
}
}
}
props.setProperty(userAgent + "." + locale, permutation);
}

StringBuilder buf = new StringBuilder();
Log.info("is Shardable: " + isShardable());

StringBuilder network_buf = new StringBuilder();
network_buf.append("NETWORK:").append(SEP);

buf.append("CACHE MANIFEST" + SEP + WELCOME + FAVICON + S_STYLE +
T_STYLE + WELCOME_STYLE + NO_CACHE_JS + EDIT_STUFF + IMAGES);
buf.append("#").append(System.currentTimeMillis()).append(SEP);

ArtifactSet artifactSet = new ArtifactSet(artifacts);

for (EmittedArtifact artifact : artifacts.find(EmittedArtifact.class)) {

if (!artifact.isPrivate()) {

if (!isRPRCall(artifact)) {

if (artifact.getPartialPath().contains("rpcPolicyManifest") ||
artifact.getPartialPath().contains("symbolMap")) {
Log.info("NOT ADDING :" + (artifact.getPartialPath() + "to cache"));
} else {
addToCache(buf, artifact);
}
} else {
network_buf.append(HOME + artifact.getPartialPath()).append(SEP);
}

}
}
String lang = (locale.equals("default") ? "" : "." + locale);
if (!onePermutation) {
EmittedArtifact artifact = emitString(logger, buf.toString() +
network_buf.toString() + FALLBACK, "/../" + userAgent + lang
+ ".fluency-up.manifest");
artifactSet.add(artifact);
Log.info("return artifactSet " + artifact.toString());
}

return artifactSet;
}

Shawn Brown

unread,
Aug 25, 2011, 6:38:19 AM8/25/11
to google-we...@googlegroups.com
.  I need to add in some .css and .gif files, as well
> as get the rpc stuff under network so I do the following:  (well I
> took some other stuff out ... but this is mostly it )


One more thing:

private void addToCache(StringBuilder buf, EmittedArtifact artifact) {
buf.append("/myApp/" + artifact.getPartialPath()).append("\n");
}

StrongSteve

unread,
Aug 25, 2011, 8:52:13 AM8/25/11
to Google Web Toolkit
So now I created a servlet filter that listens for request for the
offline manifest. Not perfect, but working.
Upon requesting the offline manifest, the filter redirects to the
version applicable for the current user-agent.

Problem is, that as soon as there is no network connection, the
offline mode does not work...

Do I have to rename the returned offline manifest to match the name
described in the HTML file? (appcache.nocache.manifest)
Because now I return appcache.nocache.manifest.gecko1_8 for example...
If it needs to be renamed how can this rename be achived with a
servlet filter?
Right now the only technique I am aware of is to send a redirect...

Thanks for your help!
Stefan


Here is the filter code....

package com.siemens.html5.whiteboard.server.filters;

import java.io.IOException;
import java.util.HashMap;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class OfflineManifestFilter implements Filter {

public static final HashMap<String, String> offlineManifestMap = new
HashMap<String, String>(6);

static {
offlineManifestMap.put("Chrome",
"appcache.nocache.manifest.gecko1_8");
offlineManifestMap.put("Firefox",
"appcache.nocache.manifest.gecko1_8");
offlineManifestMap.put("Safari",
"appcache.nocache.manifest.safari");
offlineManifestMap.put("Opera", "appcache.nocache.manifest.opera");
offlineManifestMap.put("MSIE8", "appcache.nocache.manifest.ie8");
offlineManifestMap.put("MSIE7", "appcache.nocache.manifest.ie7");
offlineManifestMap.put("MSIE6", "appcache.nocache.manifest.ie6");
}

public static final String OFFLINE_MANIFEST =
"appcache.nocache.manifest";

@Override
public void destroy() {
// TODO Auto-generated method stub

}

@Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain fc) throws IOException, ServletException {

HttpServletRequest httpRequest = (HttpServletRequest)req;

if (httpRequest.getRequestURI().contains(OFFLINE_MANIFEST)) {

String userAgent = ((HttpServletRequest) req).getHeader("User-
Agent");
System.out.println("User-Agent: " +userAgent);

for (String browser : offlineManifestMap.keySet()) {
if (userAgent.contains(browser)) {
System.out.println("Sending redirect to; "
+httpRequest.getRequestURI().replace(OFFLINE_MANIFEST,
offlineManifestMap.get(browser)));

((HttpServletResponse)res).sendRedirect(httpRequest.getRequestURI().replace(OFFLINE_MANIFEST,
offlineManifestMap.get(browser)));
}
}

}

fc.doFilter(req, res);

}

@Override
public void init(FilterConfig cfg) throws ServletException {
// TODO Auto-generated method stub

}

}

Shawn Brown

unread,
Aug 25, 2011, 4:56:59 PM8/25/11
to google-we...@googlegroups.com
> Do I have to rename the returned offline manifest to match the name
> described in the HTML file? (appcache.nocache.manifest)

No,

1) get the servlet context at init time

private ServletContext context;
@Override public void init(FilterConfig arg0) throws ServletException {
context = arg0.getServletContext();

}

2) use that to forward the file
response.setContentType("text/cache-manifest");

context.getRequestDispatcher("/" +
"appcache.nocache.manifest.gecko1_8").include(request, response);

> Because now I return appcache.nocache.manifest.gecko1_8 for example...

If the request asks for appcache.nocach.manifest, then that is what
will be returned - athough the contexts will be
appcache.nocache.manifest.gecko1_8


Is appcache.nocache.manifest.gecko1_8 OK? It doens't have to end with
.manifest ??? I don't know that.

-sowdri-

unread,
Sep 2, 2011, 9:28:07 AM9/2/11
to google-we...@googlegroups.com
Thanks Shawn and Stefan! This post helped a lot in designing this linker. 

A linker based on the discussion above could be found here:

GWT HTML5 Offline Manifest Generation Linker

Thanks,

-sowdri-

unread,
Sep 2, 2011, 9:30:55 AM9/2/11
to google-we...@googlegroups.com
You must also add this file in the same package as the above class for this to work:

appcache.manifest.template

Thanks,

-sowdri-

unread,
Sep 3, 2011, 4:37:57 AM9/3/11
to google-we...@googlegroups.com
Hi, 

The above solution was working fine, i even deployed a version and tested it live!

But suddenly i've started getting this error: 

DevMode warning: Clobbering appcache.nocache.manifest to allow debugging. Recompile before deploying your app!

And the appcache.nocache.manifest_user-agent doesn't contain the js file name! 

Any idea what is causing this???

Stephen Buergler

unread,
Sep 5, 2011, 7:29:51 AM9/5/11
to google-we...@googlegroups.com
Passing -compileReport into the gwt compiler while using this linker results in this: http://pastebin.com/dzDafk96
If it is any help this is what I made following a video about GWT. http://pastebin.com/tjTghKVA No template file necessary.
I changed the name of the outputted file so that both your linker and mine could be run side to side.
The downside in mine is that Visibility.Public is what .gwt.rpc resources are even though that should not be the case and
there it doesn't presently separate the resources by their permutation into different manifest files. If you are going to do that
though you will need to allow for permutations that differ in more ways than just useragent and locale.

I haven't really been using mine so I don't know if there is a problem that I don't include a unique ID. I figure that if there is
a change the compiled filenames will change, if there is not a change then they won't. I guess other non-gwt resources can
change though (but then why would they recompile the gwt project?).


Joshua Kappon

unread,
Apr 8, 2012, 10:16:41 AM4/8/12
to google-we...@googlegroups.com
Followed this thread, and also stumbled the same problem.
anyone solved this?

Shawn Brown

unread,
Apr 8, 2012, 8:39:03 PM4/8/12
to google-we...@googlegroups.com
Hi,

> Followed this thread, and also stumbled the same problem.
> anyone solved this?

>> DevMode warning: Clobbering appcache.nocache.manifest to allow debugging.


>> Recompile before deploying your app!
>>
>> And the appcache.nocache.manifest_user-agent doesn't contain the js file
>> name!
>>
>> Any idea what is causing this???

Sorry no idea at all. Have never seen it. I don't understand the problem.

If you need to know which js file goes into which user-agent file, you
can figure that out from this:

[actually this makes a separate permutation.properties file. I have
the same login in my linker class to make sure the pro]


import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Map;
import java.util.Properties;
import java.util.SortedMap;
import java.util.SortedSet;

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.ArtifactSet;
import com.google.gwt.core.ext.linker.CompilationResult;
import com.google.gwt.core.ext.linker.LinkerOrder;
import com.google.gwt.core.ext.linker.SelectionProperty;
import com.google.gwt.core.ext.linker.SyntheticArtifact;

/**
* This GWT linker creates a properties file which can be used to
resolve Permutation Strong name given UserAgent and
* locale.
*
* @author Etienne Lacazedieu
*/
@LinkerOrder(LinkerOrder.Order.PRE) public class
StrongNameOracleLinker extends AbstractLinker {
public static final String STRONGNAME_FILE = "permutation.properties";

@Override public String getDescription() {
return "PermutationStrongName Oracle linker";
}

@Override public ArtifactSet link(TreeLogger logger, LinkerContext

context, ArtifactSet artifacts) throws UnableToCompleteException {
artifacts = new ArtifactSet(artifacts);
ByteArrayOutputStream out = new ByteArrayOutputStream();

String permutation = null;
String locale = null;
String userAgent = null;
SelectionProperty selectionProperty = null;

Properties props = new Properties();

for (CompilationResult result : artifacts.find(CompilationResult.class)) {
permutation = result.getStrongName();

SortedSet<SortedMap<SelectionProperty, String>> propertiesMap =
result.getPropertyMap();
for (SortedMap<SelectionProperty, String> sm : propertiesMap) {
for (Map.Entry<SelectionProperty, String> e : sm.entrySet()) {
selectionProperty = e.getKey();
if ("locale".equals(selectionProperty.getName())) {
locale = e.getValue();
}
if ("user.agent".equals(selectionProperty.getName())) {
userAgent = e.getValue();
}
}
}
props.setProperty(userAgent + "." + locale, permutation);
}

try {
props.store(out, "StrongNameOracle properties file");
} catch (IOException e) { // Should generally not happen
logger.log(TreeLogger.ERROR, "Unable to store deRPC data", e);
throw new UnableToCompleteException();
}
SyntheticArtifact a = emitBytes(logger, out.toByteArray(), STRONGNAME_FILE);
artifacts.add(a);

return artifacts;
}

}


-------------------------------


so in the actual linker class I have I use


...

Properties props = new Properties();

for (CompilationResult result : artifacts.find(CompilationResult.class)) {
permutation = result.getStrongName();

SortedSet<SortedMap<SelectionProperty, String>> propertiesMap =
result.getPropertyMap();
for (SortedMap<SelectionProperty, String> sm : propertiesMap) {
for (Map.Entry<SelectionProperty, String> e : sm.entrySet()) {
selectionProperty = e.getKey();
if ("locale".equals(selectionProperty.getName())) {
locale = e.getValue();
}
if ("user.agent".equals(selectionProperty.getName())) {
userAgent = e.getValue();
}
}
}
props.setProperty(userAgent + "." + locale, permutation);
}

...

that gets the right js file into the right permutation manifest.

Is that what the question is?

shawn

Joshua Kappon

unread,
Apr 11, 2012, 9:21:48 AM4/11/12
to google-we...@googlegroups.com
Thanks,

Can you explain the relationship between the two linkers?

Joshua Kappon

unread,
Apr 11, 2012, 9:22:46 AM4/11/12
to google-we...@googlegroups.com
And also, how do you cause the linkers not to run on devMode?
Reply all
Reply to author
Forward
0 new messages