Showing posts with label linker. Show all posts
Showing posts with label linker. Show all posts

Monday, August 8, 2011

Using GWT configuration properties

One of the commenters to the blog here bring me to the nice idea to use configuration properties to configure the linker instead of sub classing the linker. I found the idea very nice and decide to play around this feature. I did this example just to demonstrate what you can do to extend the linker, which I created in my previously articles to be able to use properties as well.

First thing you need to know is how to use actually GWT configuration properties. Here is a link which describes the multi value properties but also gives you the overview about how to declare your own property. I decide to have a new property called cache.manifest which will be used to define the static files needed to be cached, which normally are not affected by the linker and for which I used sub classing before. Here the steps you need to proceed, to define your own linker:

  • Inside your <module>.gwt.xml define the configuration property by adding the following:
<define-configuration-property name="cache.manifest"
is
-multi-valued="true" />



  • After this line you can now specify the values of the property. As you can see from the definition above I define the property as multi valued, which means the property can have more then one value. To add properties do the following:

<extend-configuration-property name="cache.manifest"
value
="/Gwt2go.html" />
<extend-configuration-property name="cache.manifest"
value
="/images/*" />
<extend-configuration-property name="cache.manifest"
value
="/css/*" />

 




  • Make sure that you extend the property after you defined, otherwise you will get warning message.

Now the most important part here how to use this property from the linker. The linker gives you the current LinkerContext which has a function getConfigurationProperties(). This function contains all defined properties. Knowing this you can do the following:




  • Create a new method which reads the new configuration property:

protected String[] getPropsExtraFiles(TreeLogger logger,
LinkerContext context) {

// get the configured external properties, if any
// try to list the configuration properties here
// this files should be static external files

if (context.getConfigurationProperties().isEmpty()) {
logger.log(
TreeLogger.INFO,
"Info: No external static options "
+ " have been configured, configuration-property cache.manifest "
+ " is empty!");

return new String[0];

}
else {

Iterator
<ConfigurationProperty> it = context
.getConfigurationProperties().iterator();

while (it.hasNext()) {

ConfigurationProperty prop
= it.next();

if (prop.getName().equalsIgnoreCase(CONFIG_PROPERTY)) {

List
<String> props = prop.getValues();

return (String[]) props.toArray(new String[0]);
}

}

}

return new String[0];

}



  • now you can use the new function like this for example:

String[] cacheExtraFiles = getPropsExtraFiles(logger, context);

if (cacheExtraFiles.length == 0) {
cacheExtraFiles
= getCacheExtraFiles();
}

for (int i = 0; i < cacheExtraFiles.length; i++) {
staticResoucesSb.append(cacheExtraFiles[i]);
staticResoucesSb.append(
"\n");
}

This means if it’s property defined then use it, otherwise try to get the from the extended class.


You can find the full source code here and here! Also check the this and this, to see what was modified!


cheers

Wednesday, July 27, 2011

Writing GWT Linker for offline applications – issues

I was very surprised by the number of users visiting my last post regarding the GWT Linker which allows you to compile application manifest for offline support. There are also some issues reported, thank you very much about the feedbacks.

Now I will try to keep it short, there is issue when you compile your application using the linker from my post HERE and then you run the application and open it, you will realize that the generated manifest file is empty. This happens because of a small bug in the linker.

The linker checks if the SelectionInformation is empty and if this is the case sets the ArtifacSet to null. The problem then is that I call after that every time the function emitLandingPageCacheManifest, but because the artifact is null it generates empty manifest which overrides the one you compile.

Now I made a small fix, I am not sure if this is the best way to do it, but fix the issue for now. The fix I made was in the link() function, so that the function now looks like this:

1 @Override
2 public ArtifactSet link(TreeLogger logger, LinkerContext context,
3 ArtifactSet artifacts, boolean onePermutation)
4 throws UnableToCompleteException {
5 ArtifactSet toReturn = new ArtifactSet(artifacts);
6
7 if (onePermutation) {
8 return toReturn;
9 }
10
11 if (toReturn.find(SelectionInformation.class).isEmpty()) {
12 logger.log(TreeLogger.INFO, "Warning: Clobbering " + MANIFEST
13 + " to allow debugging. "
14 + "Recompile before deploying your app!");
15 artifacts = null;
16 } else {
17 // Create the general cache-manifest resource for the landing page:
18 toReturn.add(emitLandingPageCacheManifest(context, logger,
19 artifacts));
20 }
21 return toReturn;
22
23 };

You can get the latest code from HERE.


I hope that helps, if you have any other issues do not hesitate to post them!


cheers

Thursday, June 30, 2011

Writing GWT Linker for offline applications – part 2

NOTE: This article follows my previously post about linkers, which you can find HERE.

It is really sad that there is not enough documentation about the GWT Linkers. It is a very powerful feature and gives you a lot of possibility, I do hope that in the next documentation this will change. As I tried to extend my linker, I found also some example into the GWT Code on the current trunk. I was very happy to see that the GWT team works on the offline support as well, so maybe we will see this feature supported natively from the GWT very soon. It works also using Linkers and they plan to have one more linker which generates the cache manifest file which you can add to your <module>.gwt.xml file. For impatient of you, follow the tutorial bellow.

Before we start some few new things I learn doing my tests. The com.google.gwt.core.ext.Linker has two public methods you can override called linker(). If you use the annotation @Shardable when you create your custom linker, then the method link(TreeLogger logger, LinkerContext context, ArtifactSet artifacts, boolean onePermutation) will be called and only this method! The difference here is that you do have the variable onePermutation which true when only one permutation is called. If you use @Shardable then you have to define also your linker in a different way into your <module>.gwt.xml file, you should use this:

1 <define-linker name="manifest" class="com.gwt2go.dev.linker.ManifestLinker" />
2 <add-linker name="manifest" />
3

To save some time I based my code on the SimpleAppCacheLinker which you can find in the GWT trunk code HERE. I don’t wanted to start experiment everything from the beginning, since GWT team work on it as well and you can use the code. You should be also familiar with the HTML5 Offline Web Applications, since this example will vary depending on your project requirements. Also I change some staff in the code not really much, so here the steps.


First I extend my ManifestLinker so that it uses the source code from the SimpleAppCacheLinker and made some modification:


1
2 import java.io.IOException;
3 import java.util.Arrays;
4 import java.util.Date;
5
6 import org.apache.commons.io.IOUtils;
7
8 import com.google.gwt.core.ext.LinkerContext;
9 import com.google.gwt.core.ext.TreeLogger;
10 import com.google.gwt.core.ext.UnableToCompleteException;
11 import com.google.gwt.core.ext.linker.AbstractLinker;
12 import com.google.gwt.core.ext.linker.Artifact;
13 import com.google.gwt.core.ext.linker.ArtifactSet;
14 import com.google.gwt.core.ext.linker.EmittedArtifact;
15 import com.google.gwt.core.ext.linker.LinkerOrder;
16 import com.google.gwt.core.ext.linker.impl.SelectionInformation;
17
18 /**
19 * Manifest linker class creating application manifest for HTML5 offline web
20 * applications supporting browsers, based on code from
21 * com.google.gwt.core.linker.SimpleAppCacheLinker <br>
22 *
23 * @see <a
24 * href="http://code.google.com/p/google-web-toolkit/source/browse/trunk/dev/core/src/com/google/gwt/core/linker/SimpleAppCacheLinker.java?r=10181">SimpleAppCacheLinker</a>
25 *
26 * <br>
27 * ManifestLinker - linker for public path resources in the Application
28 * Cache.
29 * <p>
30 * To use:
31 * <ol>
32 * <li>Add {@code manifest="YOURMODULENAME/appcache.manifest"} to the
33 * {@code <html>} tag in your base html file. E.g.,
34 * {@code <html manifest="mymodule/appcache.manifest">}</li>
35 * <li>Add a mime-mapping to your web.xml file:
36 * <p>
37 *
38 * <pre>
39 * {@code <mime-mapping>
40 * <extension>manifest</extension>
41 * <mime-type>text/cache-manifest</mime-type>
42 * </mime-mapping>
43 * }
44 * </pre>
45 *
46 * </li>
47 * </ol>
48 * <p>
49 * On every compile, this linker will regenerate the appcache.manifest file
50 * with files from the public path of your module.
51 * <p>
52 * To obtain a manifest that contains other files in addition to those
53 * generated by this linker, create a class that inherits from this one and
54 * overrides {@code otherCachedFiles()}, and use it as a linker instead:
55 * <p>
56 *
57 * <pre>
58 * <blockquote>
59 * {@code @Shardable}
60 * public class MyAppCacheLinker extends ManifestLinker {
61 * {@code @Override}
62 * protected String[] otherCachedFiles() {
63 * return new String[] {"/MyApp.html","/MyApp.css"};
64 * }
65 *
66 * {@code @Override}
67 * protected String[] appCacheManifestTemplate() {
68 * return "myappcache.manifest";
69 * }
70 * }
71 * </blockquote>
72 * </pre>
73 *
74 * @author L.Pelov
75 */
76 @LinkerOrder(LinkerOrder.Order.POST)
77 public class ManifestLinker extends AbstractLinker {
78
79 private static final String MANIFEST = "appcache.manifest";
80 private static final String MANIFESTTEMPLATE = "cache.manifest.template";
81
82 @Override
83 public String getDescription() {
84 return "Application manifest linker";
85 }
86
87 @Override
88 public ArtifactSet link(TreeLogger logger, LinkerContext context,
89 ArtifactSet artifacts, boolean onePermutation)
90 throws UnableToCompleteException {
91 ArtifactSet toReturn = new ArtifactSet(artifacts);
92
93 if (onePermutation) {
94 return toReturn;
95 }
96
97 if (toReturn.find(SelectionInformation.class).isEmpty()) {
98 logger.log(TreeLogger.INFO, "Warning: Clobbering " + MANIFEST
99 + " to allow debugging. "
100 + "Recompile before deploying your app!");
101 artifacts = null;
102 }
103
104 // Create the general cache-manifest resource for the landing page:
105 toReturn.add(emitLandingPageCacheManifest(context, logger, artifacts));
106 return toReturn;
107
108 };
109
110 /**
111 * Creates the cache-manifest resource specific for the landing page.
112 *
113 * @param context
114 * the linker environment
115 * @param logger
116 * the tree logger to record to
117 * @param artifacts
118 * {@code null} to generate an empty cache manifest
119 */
120
121 private Artifact<?> emitLandingPageCacheManifest(LinkerContext context,
122 TreeLogger logger, ArtifactSet artifacts)
123 throws UnableToCompleteException {
124 StringBuilder publicSourcesSb = new StringBuilder();
125 StringBuilder staticResoucesSb = new StringBuilder();
126
127 if (artifacts != null) {
128 // Iterate over all emitted artifacts, and collect all cacheable
129 // artifacts
130 for (@SuppressWarnings("rawtypes")
131 Artifact artifact : artifacts) {
132 if (artifact instanceof EmittedArtifact) {
133 EmittedArtifact ea = (EmittedArtifact) artifact;
134 String pathName = ea.getPartialPath();
135 if (pathName.endsWith("symbolMap")
136 || pathName.endsWith(".xml.gz")
137 || pathName.endsWith("rpc.log")
138 || pathName.endsWith("gwt.rpc")
139 || pathName.endsWith("manifest.txt")
140 || pathName.startsWith("rpcPolicyManifest")) {
141 // skip these resources
142 } else {
143 publicSourcesSb.append(pathName + "\n");
144 }
145 }
146 }
147
148 String[] cacheExtraFiles = getCacheExtraFiles();
149 for (int i = 0; i < cacheExtraFiles.length; i++) {
150 staticResoucesSb.append(cacheExtraFiles[i]);
151 staticResoucesSb.append("\n");
152 }
153 }
154
155 String cacheManifestString = createCache(logger, context,
156 publicSourcesSb, staticResoucesSb);
157
158 // Create the manifest as a new artifact and return it:
159 return emitString(logger, cacheManifestString, MANIFEST);
160 }
161
162 /**
163 * Generate the application cache manifest file
164 *
165 * @param logger
166 * - the tree logger to record to
167 * @param context
168 * - the linker environment
169 * @param publicSourcesSb
170 * - contains the public resources, generated by the compilation
171 * @param staticResoucesSb
172 * - contains the static resources, defined by your subclass
173 * @return
174 * @throws UnableToCompleteException
175 */
176 protected String createCache(TreeLogger logger, LinkerContext context,
177 StringBuilder publicSourcesSb, StringBuilder staticResoucesSb)
178 throws UnableToCompleteException {
179
180 // // build cache list
181 // StringBuilder sb = new StringBuilder();
182 // sb.append("CACHE MANIFEST\n");
183 // sb.append("# Unique id #" + (new Date()).getTime() + "."
184 // + Math.random() + "\n");
185 // // we have to generate this unique id because the resources can
186 // change
187 // // but the hashed cache.html files can remain the same.
188 // sb.append("# Note: must change this every time for cache to invalidate\n");
189 // sb.append("\n");
190 // sb.append("CACHE:\n");
191 // sb.append("# Static app files\n");
192 // sb.append(staticResoucesSb.toString());
193 // sb.append("\n# Generated app files\n");
194 // sb.append(publicSourcesSb.toString());
195 // sb.append("\n\n");
196 // sb.append("# All other resources require the user to be online.\n");
197 // sb.append("NETWORK:\n");
198 // sb.append("*\n");
199
200 try {
201 String manifest = IOUtils.toString(getClass().getResourceAsStream(
202 appCacheManifestTemplate()));
203
204 // replace the placeholder with the real data
205 manifest = manifest.replace("$UNIQUEID$",
206 (new Date()).getTime() + "." + Math.random()).toString();
207
208 manifest = manifest.replace("$STATICAPPFILES$",
209 staticResoucesSb.toString());
210
211 manifest = manifest.replace("$GENAPPFILES$",
212 publicSourcesSb.toString());
213
214 logger.log(
215 TreeLogger.INFO,
216 "Be sure your landing page's <html> tag declares a manifest:"
217 + " <html manifest="
218 + context.getModuleFunctionName() + "/" + MANIFEST
219 + "\">");
220
221 return manifest;
222
223 } catch (IOException e) {
224
225 logger.log(TreeLogger.ERROR,
226 "Could not read cache manifest template.", e);
227
228 throw new UnableToCompleteException();
229 }
230 }
231
232 private String[] getCacheExtraFiles() {
233 String[] cacheExtraFiles = otherCachedFiles();
234 return cacheExtraFiles == null ? new String[0] : Arrays.copyOf(
235 cacheExtraFiles, cacheExtraFiles.length);
236 }
237
238 /**
239 * Obtains the extra files to include in the manifest. Ensures the returned
240 * array is not null.
241 */
242 protected String[] otherCachedFiles() {
243 return null;
244 }
245
246 /**
247 * Do not forget to include this file into your <html manifest="" \>
248 *
249 * @return
250 */
251 protected String appCacheManifestTemplate() {
252 return MANIFESTTEMPLATE;
253 }
254 }
255

I add one more function calls appCacheManifestTemplate() which you can override in your subclass. It gives you the possibility to use custom template file for the cache. This means you can have a simple template file with some configurations inside which are not GWT relevant and than just replace the placeholders inside with the generated GWT code. My template files cache.manifest.template looks like this:


1 CACHE MANIFEST
2 # Unique id #$UNIQUEID$
3 # Note: must change this every time for cache to invalidate
4 # L.Pelov
5
6 CACHE:
7 # Static app files
8 $STATICAPPFILES$
9
10 # Generated app files
11 $GENAPPFILES$
12
13 # All other resources require the user to be online.
14 NETWORK:
15 *
16

Inside I place the placeholders $UNIQUEID$, $STATICAPPFILES$ and $GENAPPFILES$, which then I replace in the function createCache(). Inside there is also the original code to see the difference. The template file should be created in the same namespace where your linker class was created.


Next step will be to extend this linker with your application class linker:


1 import com.google.gwt.core.ext.linker.Shardable;
2
3 /**
4 * Application Linker creates the cacheable manifest file
5 *
6 * @author L.Pelov
7 */
8 @Shardable
9 public class AppCacheManifestLinker extends ManifestLinker {
10
11 @Override
12 protected String[] otherCachedFiles() {
13 return new String[] { "/Gwt2go.html", "/Gwt2go.css" };
14 }
15
16 @Override
17 protected String appCacheManifestTemplate() {
18 return "myappcache.manifest";
19 }
20 }
21

As you can see you can defined very easy also a new static files to be cached and also have other custom templates, if you wish. If no custom template was defined then the ManifestLinker will use the default cache.manifest.template from your namespace.


As next you have to define your application manifest file into your main html file. In my case this  the Gwt2go.html where I just had to put this:


<html manifest="gwt2go/appcache.manifest">


This will tell the browser where to read the manifest file. Another configuration you NEED to do, you have to add following into your web.xml file…


1 <mime-mapping>
2 <extension>manifest</extension>
3 <mime-type>text/cache-manifest</mime-type>
4 </mime-mapping>
5

… and at last you have to put this inside your <module>.gwt.xml file…


1 <define-linker name="manifest" class="com.gwt2go.dev.linker.AppCacheManifestLinker" />
2 <add-linker name="manifest" />
3

so that the linker will be called when you compile your application.


Now we are ready to go. Compile your application and open it with a browser which support Offline Web Applications standard.


If you start the application now and open it you should see for example in Firefox this:


image





OK in my case is in GermanSmiley, but you should see the same in English as well, the first button from left allows the browser to cache the content. When you allow the browser to download the content for offline work, you then go and set-up the browser to work offline and then try the application again without the ?gwt.code on the end of the link. If everything was OK then you should be able to work with the application.


Regarding debugging and working with it. If you want to debug or test run the application in Eclipse and use the development mode links as usual. If you want that the browser refresh the cache with the latest modifications you done in your code, you have to rebuild the application. This will change the unique id and the browser knows what to do.


You can find the code from this example HERE.


To go directly to the namespace with the linker code click HERE.


I hope that helps you, cheers!