blob: 40153910ff1c595c3e7aabf065431b977b04f2ac [file] [log] [blame]
/*
* Copyright 2008 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.
*/
package com.google.gwt.resources.client.impl;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response;
import com.google.gwt.jsonp.client.JsonpRequestBuilder;
import com.google.gwt.resources.client.ExternalTextResource;
import com.google.gwt.resources.client.ResourceCallback;
import com.google.gwt.resources.client.ResourceException;
import com.google.gwt.resources.client.TextResource;
import com.google.gwt.safehtml.shared.SafeUri;
import com.google.gwt.user.client.rpc.AsyncCallback;
/**
* Implements external resource fetching of TextResources.
*/
public class ExternalTextResourcePrototype implements ExternalTextResource {
/**
* Maps the HTTP callback onto the ResourceCallback.
*/
private class ETRCallback implements RequestCallback, AsyncCallback<JavaScriptObject> {
final ResourceCallback<TextResource> callback;
public ETRCallback(ResourceCallback<TextResource> callback) {
this.callback = callback;
}
// For RequestCallback
public void onError(Request request, Throwable exception) {
onFailure(exception);
}
// For AsyncCallback
public void onFailure(Throwable exception) {
callback.onError(new ResourceException(
ExternalTextResourcePrototype.this,
"Unable to retrieve external resource", exception));
}
// For RequestCallback
public void onResponseReceived(Request request, final Response response) {
String responseText = response.getText();
// Call eval() on the object.
JavaScriptObject jso = evalObject(responseText);
onSuccess(jso);
}
// For AsyncCallback
public void onSuccess(JavaScriptObject jso) {
if (jso == null) {
callback.onError(new ResourceException(
ExternalTextResourcePrototype.this, "eval() returned null"));
return;
}
// Populate the TextResponse cache array
final String resourceText = extractString(jso, index);
cache[index] = new TextResource() {
public String getName() {
return name;
}
public String getText() {
return resourceText;
}
};
// Finish by invoking the callback
callback.onSuccess(cache[index]);
}
}
/**
* Evaluate the JSON payload. The regular expression to validate the safety of
* the payload is taken from RFC 4627 (D. Crockford).
*
* @param data the raw JSON-encapsulated string bundle
* @return the evaluated JSON object, or <code>null</code> if there is an
* error.
*/
private static native JavaScriptObject evalObject(String data) /*-{
var safe = !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
data.replace(/"(\\.|[^"\\])*"/g, '')));
if (!safe) {
return null;
}
return eval('(' + data + ')') || null;
}-*/;
/**
* Extract the specified String from a JavaScriptObject that is array-like.
*
* @param jso the JavaScriptObject returned from {@link #evalObject(String)}
* @param index the index of the string to extract
* @return the requested string, or <code>null</code> if it does not exist.
*/
private static native String extractString(JavaScriptObject jso, int index) /*-{
return (jso.length > index) && jso[index] || null;
}-*/;
/**
* This is a reference to an array nominally created in the IRB that contains
* the ExternalTextResource. It is intended to be shared between all instances
* of the ETR that have a common parent IRB.
*/
private final TextResource[] cache;
private final int index;
private final String md5Hash;
private final String name;
private final SafeUri url;
public ExternalTextResourcePrototype(String name, SafeUri url,
TextResource[] cache, int index) {
this.name = name;
this.url = url;
this.cache = cache;
this.index = index;
this.md5Hash = null;
}
public ExternalTextResourcePrototype(String name, SafeUri url,
TextResource[] cache, int index, String md5Hash) {
this.name = name;
this.url = url;
this.cache = cache;
this.index = index;
this.md5Hash = md5Hash;
}
public String getName() {
return name;
}
/**
* Possibly fire off an HTTPRequest for the text resource.
*/
public void getText(ResourceCallback<TextResource> callback)
throws ResourceException {
// If we've already parsed the JSON bundle, short-circuit.
if (cache[index] != null) {
callback.onSuccess(cache[index]);
return;
}
if (md5Hash != null) {
// If we have an md5Hash, we should be using JSONP
JsonpRequestBuilder rb = new JsonpRequestBuilder();
rb.setPredeterminedId(md5Hash);
rb.requestObject(url.asString(), new ETRCallback(callback));
} else {
RequestBuilder rb = new RequestBuilder(RequestBuilder.GET, url.asString());
try {
rb.sendRequest("", new ETRCallback(callback));
} catch (RequestException e) {
throw new ResourceException(this, "Unable to initiate request for external resource", e);
}
}
}
}