View Javadoc

1   /*
2    * Copyright 2008 Google Inc.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5    * use this file except in compliance with the License. You may obtain a copy of
6    * the License at
7    * 
8    * http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations under
14   * the License.
15   */
16  package org.vectomatic.dom.svg.impl;
17  
18  import org.vectomatic.dom.svg.OMSVGSVGElement;
19  import org.vectomatic.dom.svg.ui.ExternalSVGResource;
20  import org.vectomatic.dom.svg.ui.SVGResource;
21  import org.vectomatic.dom.svg.utils.OMSVGParser;
22  
23  import com.google.gwt.core.client.JavaScriptObject;
24  import com.google.gwt.http.client.Request;
25  import com.google.gwt.http.client.RequestBuilder;
26  import com.google.gwt.http.client.RequestCallback;
27  import com.google.gwt.http.client.RequestException;
28  import com.google.gwt.http.client.Response;
29  import com.google.gwt.resources.client.ResourceCallback;
30  import com.google.gwt.resources.client.ResourceException;
31  import com.google.gwt.safehtml.shared.SafeUri;
32  import com.google.gwt.safehtml.shared.UriUtils;
33  
34  /**
35   * Implementation of ExternalSVGResource derived from Google's original
36   * ExternalTextResourcePrototype implementation
37   * @author laaglu
38   */
39  public class ExternalSVGResourcePrototype implements ExternalSVGResource {
40  
41  	/**
42  	 * Maps the HTTP callback onto the ResourceCallback.
43  	 */
44  	private class ESRCallback implements RequestCallback {
45  		final ResourceCallback<SVGResource> callback;
46  
47  		public ESRCallback(ResourceCallback<SVGResource> callback) {
48  			this.callback = callback;
49  		}
50  
51  		public void onError(Request request, Throwable exception) {
52  			callback.onError(new ResourceException(
53  					ExternalSVGResourcePrototype.this,
54  					"Unable to retrieve external resource", exception));
55  		}
56  
57  		public void onResponseReceived(Request request, final Response response) {
58  			// Get the contents of the JSON bundle
59  			String responseText = response.getText();
60  
61  			// Call eval() on the object.
62  			JavaScriptObject jso = evalObject(responseText);
63  			if (jso == null) {
64  				callback.onError(new ResourceException(
65  						ExternalSVGResourcePrototype.this,
66  						"eval() returned null"));
67  				return;
68  			}
69  
70  			// Populate the TextResponse cache array
71  			for (int i = 0; i < cache.length; i++) {
72  				final String resourceText = extractString(jso, i);
73  				cache[i] = new SVGResource() {
74  
75  					@Override
76  					public String getName() {
77  						return name;
78  					}
79  
80  					@Override
81  					public OMSVGSVGElement getSvg() {
82  						return OMSVGParser.parse(resourceText);
83  					}
84  
85  					@Override
86  					public SafeUri getSafeUri() {
87  						return UriUtils.fromSafeConstant("data:image/svg+xml;utf8," + resourceText);
88  					}
89  
90  					@Override
91  					public String getUrl() {
92  						return "data:image/svg+xml;utf8," + resourceText;
93  					}
94  
95  				};
96  			}
97  
98  			// Finish by invoking the callback
99  			callback.onSuccess(cache[index]);
100 		}
101 	}
102 
103 	/**
104 	 * Evaluate the JSON payload. The regular expression to validate the safety
105 	 * of the payload is taken from RFC 4627 (D. Crockford).
106 	 * 
107 	 * @param data
108 	 *            the raw JSON-encapsulated string bundle
109 	 * @return the evaluated JSON object, or <code>null</code> if there is an
110 	 *         error.
111 	 */
112 	private static native JavaScriptObject evalObject(String data) /*-{
113 	    var safe = !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
114 	      data.replace(/"(\\.|[^"\\])*"/g, '')));
115 
116 	    if (!safe) {
117 	      return null;
118 	    }
119 
120 	    return eval('(' + data + ')') || null;
121 	  }-*/;
122 
123 	/**
124 	 * Extract the specified String from a JavaScriptObject that is array-like.
125 	 * @param jso
126 	 * the JavaScriptObject returned from {@link #evalObject(String)}
127 	 * @param index
128 	 * the index of the string to extract
129 	 * @return the requested string, or <code>null</code> if it does not exist.
130 	 */
131 	private static native String extractString(JavaScriptObject jso, int index) /*-{
132 	    return (jso.length > index) && jso[index] || null;
133 	  }-*/;
134 
135 	/**
136 	 * This is a reference to an array nominally created in the IRB that
137 	 * contains the ExternalSVGResource. It is intended to be shared between
138 	 * all instances of the ETR that have a common parent IRB.
139 	 */
140 	private final SVGResource[] cache;
141 	private final int index;
142 	private final String name;
143 	private final String url;
144 
145 	/**
146 	 * Constructor
147 	 * @param name The resource name
148 	 * @param url The resource URL
149 	 * @param cache A cache of loaded resource
150 	 * @param index The index for this resource in the cache
151 	 */
152 	public ExternalSVGResourcePrototype(String name, String url,
153 			SVGResource[] cache, int index) {
154 		this.name = name;
155 		this.url = url;
156 		this.cache = cache;
157 		this.index = index;
158 	}
159 
160 	/**
161 	 * Returns the SVG resource name
162 	 * @return the SVG resource name
163 	 */
164 	public String getName() {
165 		return name;
166 	}
167 
168 	/**
169 	 * Possibly fire off an HTTPRequest for the SVG resource.
170 	 * @param callback The request callback
171 	 */
172 	public void getSvg(ResourceCallback<SVGResource> callback)
173 			throws ResourceException {
174 
175 		// If we've already parsed the JSON bundle, short-circuit.
176 		if (cache[index] != null) {
177 			callback.onSuccess(cache[index]);
178 			return;
179 		}
180 
181 		// Otherwise, fire an HTTP request.
182 		RequestBuilder rb = new RequestBuilder(RequestBuilder.GET, url);
183 		try {
184 			rb.sendRequest("", new ESRCallback(callback));
185 		} catch (RequestException e) {
186 			throw new ResourceException(this,
187 					"Unable to initiate request for external resource", e);
188 		}
189 	}
190 }