You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@shindig.apache.org by et...@apache.org on 2008/03/11 10:53:01 UTC
svn commit: r635862 [3/5] - in /incubator/shindig/trunk: config/
features/core/ features/setprefs/ java/gadgets/
java/gadgets/src/main/java/org/apache/shindig/gadgets/
java/gadgets/src/main/java/org/apache/shindig/gadgets/http/
java/gadgets/src/main/ja...
Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/JsServlet.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/JsServlet.java?rev=635862&r1=635861&r2=635862&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/JsServlet.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/JsServlet.java Tue Mar 11 02:52:52 2008
@@ -17,16 +17,11 @@
*/
package org.apache.shindig.gadgets.http;
+import org.apache.shindig.gadgets.GadgetContext;
import org.apache.shindig.gadgets.GadgetFeature;
import org.apache.shindig.gadgets.GadgetFeatureFactory;
import org.apache.shindig.gadgets.GadgetFeatureRegistry;
-import org.apache.shindig.gadgets.GadgetServerConfigReader;
import org.apache.shindig.gadgets.JsLibrary;
-import org.apache.shindig.gadgets.ProcessingOptions;
-import org.apache.shindig.gadgets.RenderingContext;
-import org.apache.shindig.gadgets.SyndicatorConfig;
-import org.json.JSONException;
-import org.json.JSONObject;
import java.io.IOException;
import java.util.Arrays;
@@ -45,7 +40,6 @@
*/
public class JsServlet extends HttpServlet {
private CrossServletState servletState;
- private static final long START_TIME = System.currentTimeMillis();
@Override
public void init(ServletConfig config) throws ServletException {
@@ -58,7 +52,8 @@
// If an If-Modified-Since header is ever provided, we always say
// not modified. This is because when there actually is a change,
// cache busting should occur.
- if (req.getHeader("If-Modified-Since") != null) {
+ if (req.getHeader("If-Modified-Since") != null &&
+ req.getParameter("v") != null) {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
return;
}
@@ -88,15 +83,10 @@
GadgetFeatureRegistry registry
= servletState.getGadgetServer().getConfig().getFeatureRegistry();
if (registry.getIncludedFeatures(needed, found, missing)) {
- String containerParam = req.getParameter("c");
- RenderingContext context;
- context = "1".equals(containerParam) ?
- RenderingContext.CONTAINER :
- RenderingContext.GADGET;
-
StringBuilder jsData = new StringBuilder();
- ProcessingOptions opts = new HttpProcessingOptions(req);
+ // Probably incorrect to be using a context here...
+ GadgetContext context = new HttpGadgetContext(req);
Set<String> features = new HashSet<String>(found.size());
do {
for (GadgetFeatureRegistry.Entry entry : found) {
@@ -105,10 +95,9 @@
features.add(entry.getName());
GadgetFeatureFactory factory = entry.getFeature();
GadgetFeature feature = factory.create();
- for (JsLibrary lib : feature.getJsLibraries(context, opts)) {
- // TODO: type url js files fail here.
+ for (JsLibrary lib : feature.getJsLibraries(context)) {
if (lib.getType() != JsLibrary.Type.URL) {
- if (opts.getDebug()) {
+ if (context.getDebug()) {
jsData.append(lib.getDebugContent());
} else {
jsData.append(lib.getContent());
@@ -124,50 +113,18 @@
return;
}
- GadgetServerConfigReader serverConfig
- = servletState.getGadgetServer().getConfig();
- SyndicatorConfig syndConf = serverConfig.getSyndicatorConfig();
- JSONObject syndFeatures = syndConf.getJsonObject(opts.getSyndicator(),
- "gadgets.features");
-
- if (syndFeatures != null && context != RenderingContext.CONTAINER) {
- String[] featArray = features.toArray(new String[features.size()]);
- try {
- JSONObject featureConfig = new JSONObject(syndFeatures, featArray);
- jsData.append("gadgets.config.init(")
- .append(featureConfig.toString())
- .append(");");
- } catch (JSONException e) {
- throw new RuntimeException(e);
- }
+ if (req.getParameter("v") != null) {
+ // Versioned files get cached indefinitely
+ HttpUtil.setCachingHeaders(resp, 0);
+ } else {
+ // Unversioned files get cached for 1 hour.
+ HttpUtil.setCachingHeaders(resp, 60 * 60);
}
-
- setCachingHeaders(resp);
resp.setContentType("text/javascript; charset=utf-8");
resp.setContentLength(jsData.length());
resp.getOutputStream().write(jsData.toString().getBytes());
} else {
resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
}
- }
-
- /**
- * Sets HTTP headers that instruct the browser to cache indefinitely.
- * Implementations should take care to use cache-busting techniques on the
- * url.
- *
- * @param response The HTTP response
- */
- private void setCachingHeaders(HttpServletResponse response) {
-
- // Most browsers accept this. 2030 is the last round year before
- // the end of time.
- response.setHeader("Expires", "Tue, 01 Jan 2030 00:00:01 GMT");
-
- // IE seems to need this (10 years should be enough).
- response.setHeader("Cache-Control", "public,max-age=315360000");
-
- // Firefox requires this for certain cases.
- response.setDateHeader("Last-Modified", START_TIME);
}
}
Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/JsonRpcGadgetContext.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/JsonRpcGadgetContext.java?rev=635862&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/JsonRpcGadgetContext.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/JsonRpcGadgetContext.java Tue Mar 11 02:52:52 2008
@@ -0,0 +1,200 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.shindig.gadgets.http;
+
+import org.apache.shindig.gadgets.GadgetContext;
+import org.apache.shindig.gadgets.RenderingContext;
+import org.apache.shindig.gadgets.UserPrefs;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Extracts context from JSON input.
+ */
+public class JsonRpcGadgetContext extends GadgetContext {
+ private final URI url;
+ @Override
+ public URI getUrl() {
+ if (url == null) {
+ return super.getUrl();
+ }
+ return url;
+ }
+
+ private final Integer moduleId;
+ @Override
+ public int getModuleId() {
+ if (moduleId == null) {
+ return super.getModuleId();
+ }
+ return moduleId;
+ }
+
+
+ private final Locale locale;
+ @Override
+ public Locale getLocale() {
+ if (locale == null) {
+ return super.getLocale();
+ }
+ return locale;
+ }
+
+ /**
+ * @param obj
+ * @return The locale, if appropriate parameters are set, or null.
+ */
+ private static Locale getLocale(JSONObject obj) {
+ String language = obj.optString("language");
+ String country = obj.optString("country");
+ if (language == null || country == null) {
+ return null;
+ }
+ return new Locale(language, country);
+ }
+
+ private final RenderingContext renderingContext;
+ @Override
+ public RenderingContext getRenderingContext() {
+ if (renderingContext == null) {
+ return super.getRenderingContext();
+ }
+ return renderingContext;
+ }
+
+ private final Boolean ignoreCache;
+ @Override
+ public boolean getIgnoreCache() {
+ if (ignoreCache == null) {
+ return super.getIgnoreCache();
+ }
+ return ignoreCache;
+ }
+
+ private final String syndicator;
+ @Override
+ public String getSyndicator() {
+ if (syndicator == null) {
+ return super.getSyndicator();
+ }
+ return syndicator;
+ }
+
+ private final Boolean debug;
+ @Override
+ public boolean getDebug() {
+ if (debug == null) {
+ return super.getDebug();
+ }
+ return debug;
+ }
+
+ private final String view;
+ @Override
+ public String getView() {
+ if (view == null) {
+ return super.getView();
+ }
+ return view;
+ }
+
+ private final UserPrefs userPrefs;
+ @Override
+ public UserPrefs getUserPrefs() {
+ if (userPrefs == null) {
+ return super.getUserPrefs();
+ }
+ return userPrefs;
+ }
+
+ /**
+ * @param json
+ * @return UserPrefs, if any are set for this request.
+ * @throws JSONException
+ */
+ @SuppressWarnings("unchecked")
+ private static UserPrefs getUserPrefs(JSONObject json) throws JSONException {
+ JSONObject prefs = json.optJSONObject("prefs");
+ if (prefs == null) {
+ return null;
+ }
+ Map<String, String> p = new HashMap<String, String>();
+ Iterator i = prefs.keys();
+ while (i.hasNext()) {
+ String key = (String)i.next();
+ p.put(key, prefs.getString(key));
+ }
+ return new UserPrefs(p);
+ }
+
+ /**
+ *
+ * @param json
+ * @return URL from the request, or null if not present
+ * @throws JSONException
+ */
+ private static URI getUrl(JSONObject json) throws JSONException {
+ try {
+ String url = json.getString("url");
+ return new URI(url);
+ } catch (URISyntaxException e) {
+ return null;
+ }
+ }
+
+ /**
+ * @param json
+ * @return module id from the request, or null if not present
+ * @throws JSONException
+ */
+ private static Integer getModuleId(JSONObject json) throws JSONException {
+ if (json.has("moduleId")) {
+ return Integer.valueOf(json.getInt("moduleId"));
+ }
+ return null;
+ }
+
+ /**
+ * @param context
+ * @param gadget
+ * @throws JSONException
+ */
+ public JsonRpcGadgetContext(JSONObject context, JSONObject gadget)
+ throws JSONException {
+ url = getUrl(gadget);
+ moduleId = getModuleId(gadget);
+ userPrefs = getUserPrefs(gadget);
+
+ locale = getLocale(context);
+ view = context.optString("view");
+ ignoreCache = context.optBoolean("ignoreCache");
+ syndicator = context.optString("syndicator");
+ debug = context.optBoolean("debug");
+ renderingContext = RenderingContext.CONTAINER;
+ }
+}
Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/JsonRpcGadgetJob.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/JsonRpcGadgetJob.java?rev=635862&r1=635861&r2=635862&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/JsonRpcGadgetJob.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/JsonRpcGadgetJob.java Tue Mar 11 02:52:52 2008
@@ -20,14 +20,10 @@
package org.apache.shindig.gadgets.http;
import org.apache.shindig.gadgets.Gadget;
+import org.apache.shindig.gadgets.GadgetContext;
+import org.apache.shindig.gadgets.GadgetException;
import org.apache.shindig.gadgets.GadgetServer;
-import org.apache.shindig.gadgets.GadgetView;
-import org.apache.shindig.gadgets.RenderingContext;
-import org.apache.shindig.gadgets.UserPrefs;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Locale;
import java.util.concurrent.Callable;
/**
@@ -35,8 +31,7 @@
*/
public class JsonRpcGadgetJob implements Callable<Gadget> {
private final GadgetServer gadgetServer;
- private final JsonRpcContext context;
- private final JsonRpcGadget gadget;
+ private final GadgetContext context;
/**
* {@inheritDoc}
@@ -44,28 +39,16 @@
* @throws RpcException
*/
public Gadget call() throws RpcException {
- GadgetView.ID gadgetId;
try {
- gadgetId = new Gadget.GadgetId(new URI(gadget.getUrl()),
- gadget.getModuleId());
- return gadgetServer.processGadget(gadgetId,
- new UserPrefs(gadget.getUserPrefs()),
- new Locale(context.getLanguage(),
- context.getCountry()),
- RenderingContext.CONTAINER,
- new JsonRpcProcessingOptions(context));
- } catch (URISyntaxException e) {
- throw new RpcException(gadget, "Bad url");
- } catch (GadgetServer.GadgetProcessException e) {
- throw new RpcException(gadget, e);
+ return gadgetServer.processGadget(context);
+ } catch (GadgetException e) {
+ throw new RpcException(context, e);
}
}
public JsonRpcGadgetJob(GadgetServer gadgetServer,
- JsonRpcContext context,
- JsonRpcGadget gadget) {
+ GadgetContext context) {
this.gadgetServer = gadgetServer;
this.context = context;
- this.gadget = gadget;
}
}
Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/JsonRpcRequest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/JsonRpcRequest.java?rev=635862&r1=635861&r2=635862&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/JsonRpcRequest.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/JsonRpcRequest.java Tue Mar 11 02:52:52 2008
@@ -20,15 +20,18 @@
package org.apache.shindig.gadgets.http;
import org.apache.shindig.gadgets.Gadget;
+import org.apache.shindig.gadgets.GadgetContext;
import org.apache.shindig.gadgets.GadgetException;
import org.apache.shindig.gadgets.GadgetServer;
-import org.apache.shindig.gadgets.GadgetSpec;
-import org.apache.shindig.gadgets.ProcessingOptions;
+import org.apache.shindig.gadgets.spec.GadgetSpec;
+import org.apache.shindig.gadgets.spec.ModulePrefs;
+import org.apache.shindig.gadgets.spec.UserPref;
+import org.apache.shindig.gadgets.spec.View;
+
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
-import java.net.URI;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
@@ -42,8 +45,7 @@
*
*/
public class JsonRpcRequest {
- private final JsonRpcContext context;
- private final List<JsonRpcGadget> gadgets;
+ private final List<GadgetContext> gadgets;
/**
* Processes the request and returns a JSON object
@@ -59,83 +61,59 @@
CompletionService<Gadget> processor =
new ExecutorCompletionService<Gadget>(server.getConfig().getExecutor());
- for (JsonRpcGadget gadget : gadgets) {
- processor.submit(new JsonRpcGadgetJob(server, context, gadget));
+ for (GadgetContext gadget : gadgets) {
+ processor.submit(new JsonRpcGadgetJob(server, gadget));
}
- ProcessingOptions options = new JsonRpcProcessingOptions(context);
-
int numJobs = gadgets.size();
do {
try {
- Gadget outGadget = processor.take().get();
+ Gadget gadget = processor.take().get();
JSONObject gadgetJson = new JSONObject();
- if (outGadget.getTitleURI() != null) {
- gadgetJson.put("titleUrl", outGadget.getTitleURI().toString());
+ GadgetSpec spec = gadget.getSpec();
+ ModulePrefs prefs = spec.getModulePrefs();
+
+ JSONObject views = new JSONObject();
+ for (View view : spec.getViews().values()) {
+ views.put(view.getName(), new JSONObject()
+ // .put("content", view.getContent())
+ .put("type", view.getType().toString().toLowerCase())
+ .put("quirks", view.getQuirks()));
}
- gadgetJson.put("url", outGadget.getId().getURI().toString())
- .put("moduleId", outGadget.getId().getModuleId())
- .put("title", outGadget.getTitle())
- .put("contentType",
- outGadget.getView(options.getView()).getType()
- .toString().toLowerCase());
// Features.
- Set<String> feats = outGadget.getRequires().keySet();
+ Set<String> feats = prefs.getFeatures().keySet();
String[] features = feats.toArray(new String[feats.size()]);
- gadgetJson.put("features", features);
- JSONObject prefs = new JSONObject();
+ JSONObject userPrefs = new JSONObject();
// User pref specs
- for (GadgetSpec.UserPref pref : outGadget.getUserPrefs()) {
+ for (UserPref pref : spec.getUserPrefs()) {
JSONObject up = new JSONObject()
.put("displayName", pref.getDisplayName())
.put("type", pref.getDataType().toString().toLowerCase())
.put("default", pref.getDefaultValue())
.put("enumValues", pref.getEnumValues());
- prefs.put(pref.getName(), up);
- }
- gadgetJson.put("userPrefs", prefs);
-
- // Content
- String iframeUrl = servletState.getIframeUrl(outGadget, options);
- gadgetJson.put("content", iframeUrl);
-
- // Extended spec data.
- String directoryTitle = outGadget.getDirectoryTitle();
- if (directoryTitle != null) {
- gadgetJson.put("directoryTitle", directoryTitle);
- }
-
- URI thumbnail = outGadget.getThumbnail();
- if (thumbnail != null) {
- gadgetJson.put("thumbnail", thumbnail.toString());
- }
-
- URI screenshot = outGadget.getScreenshot();
- if (screenshot != null) {
- gadgetJson.put("screenshot", screenshot.toString());
- }
-
- String author = outGadget.getAuthor();
- if (author != null) {
- gadgetJson.put("author", author);
- }
-
- String authorEmail = outGadget.getAuthorEmail();
- if (authorEmail != null) {
- gadgetJson.put("authorEmail", authorEmail);
- }
-
- // Categories
- List<String> cats = outGadget.getCategories();
- if (cats != null) {
- String[] categories = cats.toArray(new String[cats.size()]);
- gadgetJson.put("categories", outGadget.getCategories().toArray());
+ userPrefs.put(pref.getName(), up);
}
+ gadgetJson.put("iframeUrl", servletState.getIframeUrl(gadget))
+ .put("url", gadget.getContext().getUrl().toString())
+ .put("moduleId", gadget.getContext().getModuleId())
+ .put("title", prefs.getTitle())
+ .put("titleUrl", prefs.getTitleUrl().toString())
+ .put("views", views)
+ .put("features", features)
+ .put("userPrefs", userPrefs)
+ // extended meta data
+ .put("directoryTitle", prefs.getDirectoryTitle())
+ .put("thumbnail", prefs.getThumbnail().toString())
+ .put("screenshot", prefs.getScreenshot().toString())
+ .put("author", prefs.getAuthor())
+ .put("authorEmail", prefs.getAuthorEmail())
+ .put("categories", prefs.getCategories())
+ .put("screenshot", prefs.getScreenshot().toString());
out.append("gadgets", gadgetJson);
} catch (InterruptedException e) {
throw new RpcException("Incomplete processing", e);
@@ -146,21 +124,18 @@
RpcException e = (RpcException)ee.getCause();
// Just one gadget failed; mark it as such.
try {
- JsonRpcGadget gadget = e.getGadget();
+ GadgetContext context = e.getContext();
- if (gadget == null) {
+ if (context == null) {
throw e;
}
JSONObject errorObj = new JSONObject();
- errorObj.put("url", gadget.getUrl())
- .put("moduleId", gadget.getModuleId());
- if (e.getCause() instanceof GadgetServer.GadgetProcessException) {
- GadgetServer.GadgetProcessException gpe
- = (GadgetServer.GadgetProcessException)e.getCause();
- for (GadgetException ge : gpe.getComponents()) {
- errorObj.append("errors", ge.getMessage());
- }
+ errorObj.put("url", context.getUrl())
+ .put("moduleId", context.getModuleId());
+ if (e.getCause() instanceof GadgetException) {
+ GadgetException gpe = (GadgetException)e.getCause();
+ errorObj.append("errors", gpe.getMessage());
} else {
errorObj.append("errors", e.getMessage());
}
@@ -186,11 +161,10 @@
if (gadgets.length() == 0) {
throw new RpcException("No gadgets requested.");
}
- this.context = new JsonRpcContext(context);
- List<JsonRpcGadget> gadgetList = new LinkedList<JsonRpcGadget>();
+ List<GadgetContext> gadgetList = new LinkedList<GadgetContext>();
for (int i = 0, j = gadgets.length(); i < j; ++i) {
- gadgetList.add(new JsonRpcGadget(gadgets.getJSONObject(i)));
+ gadgetList.add(new JsonRpcGadgetContext(context, gadgets.getJSONObject(i)));
}
this.gadgets = Collections.unmodifiableList(gadgetList);
} catch (JSONException e) {
Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/ProxyHandler.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/ProxyHandler.java?rev=635862&r1=635861&r2=635862&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/ProxyHandler.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/ProxyHandler.java Tue Mar 11 02:52:52 2008
@@ -21,11 +21,11 @@
import org.apache.shindig.gadgets.GadgetException;
import org.apache.shindig.gadgets.GadgetSigner;
import org.apache.shindig.gadgets.GadgetToken;
-import org.apache.shindig.gadgets.ProcessingOptions;
import org.apache.shindig.gadgets.RemoteContent;
import org.apache.shindig.gadgets.RemoteContentFetcher;
import org.apache.shindig.gadgets.RemoteContentRequest;
import org.apache.shindig.util.InputStreamConsumer;
+
import org.json.JSONException;
import org.json.JSONObject;
@@ -40,8 +40,10 @@
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@@ -52,25 +54,24 @@
private final RemoteContentFetcher fetcher;
+ private final static Set<String> DISALLOWED_RESPONSE_HEADERS
+ = new HashSet<String>();
+ static {
+ DISALLOWED_RESPONSE_HEADERS.add("set-cookie");
+ DISALLOWED_RESPONSE_HEADERS.add("content-length");
+ }
+
public ProxyHandler(RemoteContentFetcher fetcher) {
this.fetcher = fetcher;
}
public void fetchJson(HttpServletRequest request,
HttpServletResponse response,
- GadgetSigner signer)
+ CrossServletState state)
throws ServletException, IOException, GadgetException {
- GadgetToken token = extractAndValidateToken(request, signer);
- String url = request.getParameter("url");
- URL originalUrl = validateUrl(url);
- URL signedUrl = signUrl(originalUrl, token, request);
// Fetch the content and convert it into JSON.
- // TODO: Fetcher needs to handle variety of HTTP methods.
- RemoteContent results = fetchContent(signedUrl,
- request,
- new HttpProcessingOptions(request));
-
+ RemoteContent results = fetchContent(request, state);
response.setStatus(results.getHttpStatusCode());
if (results.getHttpStatusCode() == HttpServletResponse.SC_OK) {
String output;
@@ -79,14 +80,14 @@
JSONObject resp = new JSONObject()
.put("body", results.getResponseAsString())
.put("rc", results.getHttpStatusCode());
- String json = new JSONObject().put(url, resp).toString();
- output = UNPARSEABLE_CRUFT + json;
+ String url = request.getParameter("url");
+ JSONObject json = new JSONObject().put(url, resp);
+ output = UNPARSEABLE_CRUFT + json.toString();
} catch (JSONException e) {
output = "";
}
-
- setCachingHeaders(response);
response.setContentType("application/json; charset=utf-8");
+ response.setHeader("Pragma", "no-cache");
response.setHeader("Content-Disposition", "attachment;filename=p.txt");
response.getWriter().write(output);
}
@@ -94,31 +95,28 @@
public void fetch(HttpServletRequest request,
HttpServletResponse response,
- GadgetSigner signer)
+ CrossServletState state)
throws ServletException, IOException, GadgetException {
- GadgetToken token = extractAndValidateToken(request, signer);
- URL originalUrl = validateUrl(request.getParameter("url"));
- URL signedUrl = signUrl(originalUrl, token, request);
-
- // TODO: Fetcher needs to handle variety of HTTP methods.
- RemoteContent results = fetchContent(signedUrl,
- request,
- new HttpProcessingOptions(request));
+ RemoteContent results = fetchContent(request, state);
+
+ if (request.getHeader("If-Modified-Since") != null) {
+ response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+ return;
+ }
int status = results.getHttpStatusCode();
response.setStatus(status);
if (status == HttpServletResponse.SC_OK) {
- // Fill out the response.
- setCachingHeaders(response);
Map<String, List<String>> headers = results.getAllHeaders();
+ if (headers.get("Cache-Control") == null) {
+ // Cache for 1 hour by default for images.
+ HttpUtil.setCachingHeaders(response, 60 * 60);
+ }
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
String name = entry.getKey();
List<String> values = entry.getValue();
- if (name != null
- && values != null
- && name.compareToIgnoreCase("Cache-Control") != 0
- && name.compareToIgnoreCase("Expires") != 0
- && name.compareToIgnoreCase("Content-Length") != 0) {
+ if (name != null && values != null
+ && !DISALLOWED_RESPONSE_HEADERS.contains(name.toLowerCase())) {
for (String value : values) {
response.addHeader(name, value);
}
@@ -131,66 +129,75 @@
/**
* Fetch the content for a request
+ *
+ * @param request
*/
@SuppressWarnings("unchecked")
- private RemoteContent fetchContent(URL signedUrl,
- HttpServletRequest request,
- ProcessingOptions procOptions)
- throws ServletException {
+ private RemoteContent fetchContent(HttpServletRequest request,
+ CrossServletState state) throws ServletException, GadgetException {
String encoding = request.getCharacterEncoding();
if (encoding == null) {
encoding = "UTF-8";
}
+
try {
- if ("POST".equals(request.getMethod())) {
- String method = getParameter(request, "httpMethod", "GET");
- String postData = URLDecoder.decode(
- getParameter(request, "postData", ""), encoding);
+ URL originalUrl = validateUrl(request.getParameter("url"));
+ GadgetSigner signer = state.getGadgetSigner();
+ URL signedUrl;
+ if ("signed".equals(request.getParameter("authz"))) {
+ GadgetToken token = extractAndValidateToken(request, signer);
+ if (token == null) {
+ return new RemoteContent(HttpServletResponse.SC_UNAUTHORIZED,
+ "Invalid security token.".getBytes(), null);
+ }
+ signedUrl = signUrl(originalUrl, token, request);
+ } else {
+ signedUrl = originalUrl;
+ }
+ String method = request.getMethod();
+ Map<String, List<String>> headers = null;
+ byte[] postBody = null;
+
+ if ("POST".equals(method)) {
+ method = getParameter(request, "httpMethod", "GET");
+ postBody = URLDecoder.decode(
+ getParameter(request, "postData", ""), encoding).getBytes();
- Map<String, List<String>> headers;
String headerData = request.getParameter("headers");
- if (headerData == null) {
+ if (headerData == null || headerData.length() == 0) {
headers = Collections.emptyMap();
} else {
- if (headerData.length() == 0) {
- headers = Collections.emptyMap();
- } else {
- // We actually only accept single key value mappings now.
- headers = new HashMap<String, List<String>>();
- String[] headerList = headerData.split("&");
- for (String header : headerList) {
- String[] parts = header.split("=");
- if (parts.length != 2) {
- // Malformed headers
- return RemoteContent.ERROR;
- }
- headers.put(URLDecoder.decode(parts[0], encoding),
- Arrays.asList(URLDecoder.decode(parts[1], encoding)));
+ // We actually only accept single key value mappings now.
+ headers = new HashMap<String, List<String>>();
+ String[] headerList = headerData.split("&");
+ for (String header : headerList) {
+ String[] parts = header.split("=");
+ if (parts.length != 2) {
+ // Malformed headers
+ return RemoteContent.ERROR;
}
+ headers.put(URLDecoder.decode(parts[0], encoding),
+ Arrays.asList(URLDecoder.decode(parts[1], encoding)));
}
}
-
- removeUnsafeHeaders(headers);
-
- RemoteContentRequest req = new RemoteContentRequest(
- signedUrl.toURI(), headers, postData.getBytes());
- if ("POST".equals(method)) {
- return fetcher.fetchByPost(req, procOptions);
- } else {
- return fetcher.fetch(req, procOptions);
- }
} else {
- Map<String, List<String>> headers = new HashMap<String, List<String>>();
+ postBody = null;
+ headers = new HashMap<String, List<String>>();
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String header = headerNames.nextElement();
headers.put(header, Collections.list(request.getHeaders(header)));
}
- removeUnsafeHeaders(headers);
- RemoteContentRequest req
- = new RemoteContentRequest(signedUrl.toURI(), headers);
- return fetcher.fetch(req, procOptions);
}
+
+ removeUnsafeHeaders(headers);
+
+ RemoteContentRequest.Options options
+ = new RemoteContentRequest.Options();
+ options.ignoreCache = "1".equals(request.getParameter("nocache"));
+ RemoteContentRequest req = new RemoteContentRequest(
+ method, signedUrl.toURI(), headers, postBody, options);
+ return fetcher.fetch(req);
} catch (UnsupportedEncodingException e) {
throw new ServletException(e);
} catch (URISyntaxException e) {
@@ -207,7 +214,7 @@
final String[] badHeaders = new String[] {
// No legitimate reason to over ride these.
// TODO: We probably need to test variations as well.
- "Host", "Accept-Encoding", "Accept"
+ "Host"
};
for (String bad : badHeaders) {
headers.remove(bad);
@@ -215,33 +222,36 @@
}
/**
- * Validates that the given url is valid for this reques.t
+ * Validates that the given url is valid for this request.
*
- * @param url
+ * @param urlToValidate
* @return A URL object of the URL
* @throws ServletException if the URL fails security checks or is malformed.
*/
- private URL validateUrl(String url) throws ServletException {
- if (url == null) {
+ private URL validateUrl(String urlToValidate) throws ServletException {
+ if (urlToValidate == null) {
throw new ServletException("url parameter is missing.");
}
try {
- URI origin = new URI(url);
- if (origin.getScheme() == null) {
- throw new ServletException("Invalid URL " + origin.toString());
+
+ URI url = new URI(urlToValidate);
+
+ if (url.getScheme() == null) {
+ throw new ServletException("Invalid URL " + url.toString());
}
- if (!origin.getScheme().equals("http")) {
- throw new ServletException("Unsupported scheme: " + origin.getScheme());
+ if (!url.getScheme().equals("http")) {
+ throw new ServletException("Unsupported scheme: " + url.getScheme());
}
- if (origin.getPath() == null || origin.getPath().length() == 0) {
+ if (url.getPath() == null || url.getPath().length() == 0) {
// Forcibly set the path to "/" if it is empty
- origin = new URI(origin.getScheme(),
- origin.getUserInfo(), origin.getHost(),
- origin.getPort(),
- "/", origin.getQuery(),
- origin.getFragment());
+ url = new URI(url.getScheme(),
+ url.getUserInfo(),
+ url.getHost(),
+ url.getPort(),
+ "/", url.getQuery(),
+ url.getFragment());
}
- return origin.toURL();
+ return url.toURL();
} catch (URISyntaxException use) {
throw new ServletException("Malformed URL " + use.getMessage());
} catch (MalformedURLException mfe) {
@@ -250,6 +260,7 @@
}
/**
+ * @param request
* @return A valid token for the given input.
* @throws GadgetException
*/
@@ -263,26 +274,12 @@
}
/**
- * Sets HTTP caching headers
- *
- * @param response The HTTP response
- */
- private void setCachingHeaders(HttpServletResponse response) {
- // TODO: Re-implement caching behavior if appropriate.
- response.setHeader("Cache-Control", "private; max-age=0");
- response.setDateHeader("Expires", System.currentTimeMillis() - 30);
- }
-
- /**
* Sign a URL with a GadgetToken if needed
* @return The signed url.
*/
@SuppressWarnings("unchecked")
private URL signUrl(URL originalUrl, GadgetToken token,
HttpServletRequest request) throws GadgetException {
- if (token == null || !"signed".equals(request.getParameter("authz"))) {
- return originalUrl;
- }
String method = getParameter(request, "httpMethod", "GET");
return token.signUrl(originalUrl, method, request.getParameterMap());
}
Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/ProxyServlet.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/ProxyServlet.java?rev=635862&r1=635861&r2=635862&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/ProxyServlet.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/ProxyServlet.java Tue Mar 11 02:52:52 2008
@@ -18,8 +18,8 @@
*/
package org.apache.shindig.gadgets.http;
-import org.apache.shindig.gadgets.GadgetServerConfigReader;
import org.apache.shindig.gadgets.GadgetException;
+import org.apache.shindig.gadgets.GadgetServerConfigReader;
import java.io.IOException;
import java.util.logging.Level;
@@ -53,11 +53,9 @@
String output = request.getParameter("output");
try {
if ("js".equals(output)) {
- proxyHandler.fetchJson(
- request, response, servletState.getGadgetSigner(request));
+ proxyHandler.fetchJson(request, response, servletState);
} else {
- proxyHandler.fetch(
- request, response, servletState.getGadgetSigner(request));
+ proxyHandler.fetch(request, response, servletState);
}
} catch (GadgetException ge) {
outputError(ge, response);
@@ -65,13 +63,14 @@
}
@Override
- protected void doPost(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
+ protected void doPost(HttpServletRequest request,
+ HttpServletResponse response) throws ServletException, IOException {
// Currently they are identical
doGet(request, response);
}
- private void outputError(GadgetException excep, HttpServletResponse resp) throws IOException {
+ private void outputError(GadgetException excep, HttpServletResponse resp)
+ throws IOException {
StringBuilder err = new StringBuilder();
err.append(excep.getCode().toString());
err.append(' ');
Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/RpcException.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/RpcException.java?rev=635862&r1=635861&r2=635862&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/RpcException.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/RpcException.java Tue Mar 11 02:52:52 2008
@@ -19,38 +19,40 @@
package org.apache.shindig.gadgets.http;
+import org.apache.shindig.gadgets.GadgetContext;
+
/**
* Contains RPC-specific exceptions.
*/
public class RpcException extends Exception {
- private final JsonRpcGadget gadget;
+ private final GadgetContext context;
- public JsonRpcGadget getGadget() {
- return gadget;
+ public GadgetContext getContext() {
+ return context;
}
public RpcException(String message) {
super(message);
- gadget = null;
+ context = null;
}
public RpcException(String message, Throwable cause) {
super(message, cause);
- gadget = null;
+ context = null;
}
- public RpcException(JsonRpcGadget gadget, Throwable cause) {
+ public RpcException(GadgetContext context, Throwable cause) {
super(cause);
- this.gadget = gadget;
+ this.context = context;
}
- public RpcException(JsonRpcGadget gadget, String message) {
+ public RpcException(GadgetContext context, String message) {
super(message);
- this.gadget = gadget;
+ this.context = context;
}
- public RpcException(JsonRpcGadget gadget, String message, Throwable cause) {
+ public RpcException(GadgetContext context, String message, Throwable cause) {
super(message, cause);
- this.gadget = gadget;
+ this.context = context;
}
}
Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/Feature.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/Feature.java?rev=635862&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/Feature.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/Feature.java Tue Mar 11 02:52:52 2008
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.shindig.gadgets.spec;
+import org.apache.shindig.util.XmlUtil;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Represents a Require or Optional tag.
+ * No substitutions on any fields.
+ */
+public class Feature {
+ /**
+ * Require@feature
+ * Optional@feature
+ */
+ private final String name;
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Require.Param
+ * Optional.Param
+ *
+ * Flattened into a map where Param@name is the key and Param content is
+ * the value.
+ */
+ private final Map<String, String> params;
+ public Map<String, String> getParams() {
+ return params;
+ }
+
+ /**
+ * Whether this is a Require or an Optional feature.
+ */
+ private final boolean required;
+ public boolean getRequired() {
+ return required;
+ }
+
+ /**
+ * Produces an xml representation of the feature.
+ */
+ @Override
+ public String toString() {
+ StringBuilder buf = new StringBuilder();
+ buf.append(required ? "<Require" : "<Optional")
+ .append(" feature=\"")
+ .append(name)
+ .append("\">");
+ for (Map.Entry<String, String> entry : params.entrySet()) {
+ buf.append("\n<Param name=\"")
+ .append(entry.getKey())
+ .append("\">")
+ .append(entry.getValue())
+ .append("</Param>");
+ }
+ buf.append(required ? "</Require>" : "</Optional>");
+ return buf.toString();
+ }
+
+ /**
+ * Creates a new Feature from an xml node.
+ *
+ * @param feature The feature to create
+ * @throws SpecParserException When the Require or Optional tag is not valid
+ */
+ public Feature(Element feature) throws SpecParserException {
+ this.required = feature.getNodeName().equals("Require");
+ String name = XmlUtil.getAttribute(feature, "feature");
+ if (name == null) {
+ throw new SpecParserException(
+ (required ? "Require" : "Optional") +"@feature is required.");
+ }
+ this.name = name;
+ NodeList children = feature.getElementsByTagName("Param");
+ if (children.getLength() > 0) {
+ Map<String, String> params = new HashMap<String, String>();
+ for (int i = 0, j = children.getLength(); i < j; ++i) {
+ Element param = (Element)children.item(i);
+ String paramName = XmlUtil.getAttribute(param, "name");
+ if (paramName == null) {
+ throw new SpecParserException("Param@name is required");
+ }
+ params.put(paramName, param.getTextContent());
+ }
+ this.params = Collections.unmodifiableMap(params);
+ } else {
+ this.params = Collections.emptyMap();
+ }
+ }
+}
Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/GadgetSpec.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/GadgetSpec.java?rev=635862&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/GadgetSpec.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/GadgetSpec.java Tue Mar 11 02:52:52 2008
@@ -0,0 +1,235 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.shindig.gadgets.spec;
+
+import org.apache.shindig.gadgets.Substitutions;
+import org.apache.shindig.util.HashUtil;
+import org.apache.shindig.util.XmlException;
+import org.apache.shindig.util.XmlUtil;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Represents a gadget specification root element (Module).
+ * @see <a href="http://code.google.com/apis/gadgets/docs/gadgets-extended-xsd.html">gadgets spec</a>
+ */
+public class GadgetSpec {
+ public static final String DEFAULT_VIEW = "default";
+ public static final Locale DEFAULT_LOCALE = new Locale("all", "ALL");
+
+ /**
+ * The url for this gadget spec.
+ */
+ private final URI url;
+ public URI getUrl() {
+ return url;
+ }
+
+ /**
+ * A checksum of the gadget's content.
+ */
+ private final String checksum;
+ public String getChecksum() {
+ return checksum;
+ }
+
+ /**
+ * ModulePrefs
+ */
+ private ModulePrefs modulePrefs;
+ public ModulePrefs getModulePrefs() {
+ return modulePrefs;
+ }
+
+ /**
+ * UserPref
+ */
+ private List<UserPref> userPrefs;
+ public List<UserPref> getUserPrefs() {
+ return userPrefs;
+ }
+
+ /**
+ * Content
+ * Mapping is view -> Content section.
+ */
+ private Map<String, View> views;
+ public Map<String, View> getViews() {
+ return views;
+ }
+
+ /**
+ * Retrieves a single view by name.
+ *
+ * @param name The name of the view you want to see
+ * @return The view object, if it exists, or null.
+ */
+ public View getView(String name) {
+ View view = views.get(name);
+ if (view == null && !name.equals(DEFAULT_VIEW)) {
+ return views.get(DEFAULT_VIEW);
+ }
+ return view;
+ }
+
+ /**
+ * Performs substitutions on the spec. See individual elements for
+ * details on what gets substituted.
+ *
+ * @param substituter
+ * @return The substituted spec.
+ */
+ public GadgetSpec substitute(Substitutions substituter) {
+ GadgetSpec spec = new GadgetSpec(this);
+ spec.modulePrefs = modulePrefs.substitute(substituter);
+ if (userPrefs.size() == 0) {
+ spec.userPrefs = Collections.emptyList();
+ } else {
+ List<UserPref> prefs = new ArrayList<UserPref>(userPrefs.size());
+ for (UserPref pref : userPrefs) {
+ prefs.add(pref.substitute(substituter));
+ }
+ spec.userPrefs = Collections.unmodifiableList(prefs);
+ }
+ Map<String, View> viewMap = new HashMap<String, View>(views.size());
+ for (View view : views.values()) {
+ viewMap.put(view.getName(), view.substitute(substituter));
+ }
+ spec.views = Collections.unmodifiableMap(viewMap);
+
+ return spec;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<Module>\n")
+ .append(modulePrefs).append("\n");
+ for (UserPref pref : userPrefs) {
+ buf.append(pref).append("\n");
+ }
+ for (Map.Entry<String, View> view : views.entrySet()) {
+ buf.append(view.getValue()).append("\n");
+ }
+ buf.append("</Module>");
+ return buf.toString();
+ }
+
+ /**
+ * Creates a new Module from the given xml input.
+ *
+ * @param url
+ * @param xml
+ * @throws SpecParserException
+ */
+ public GadgetSpec(URI url, String xml) throws SpecParserException {
+ Element doc;
+ try {
+ doc = XmlUtil.parse(xml);
+ } catch (XmlException e) {
+ throw new SpecParserException(e);
+ }
+ this.url = url;
+
+ // This might not be good enough; should we take message bundle changes
+ // into account?
+ this.checksum = HashUtil.checksum(xml.getBytes());
+
+ NodeList children = doc.getChildNodes();
+
+ ModulePrefs modulePrefs = null;
+ List<UserPref> userPrefs = new LinkedList<UserPref>();
+ Map<String, List<Element>> views = new HashMap<String, List<Element>>();
+ for (int i = 0, j = children.getLength(); i < j; ++i) {
+ Node child = children.item(i);
+ if (!(child instanceof Element)) {
+ continue;
+ }
+ Element element = (Element)child;
+ String name = element.getTagName();
+ if ("ModulePrefs".equals(name)) {
+ if (modulePrefs == null) {
+ modulePrefs = new ModulePrefs(element, url);
+ } else {
+ throw new SpecParserException(
+ "Only 1 ModulePrefs is allowed.");
+ }
+ }
+ if ("UserPref".equals(name)) {
+ UserPref pref = new UserPref(element);
+ userPrefs.add(pref);
+ }
+ if ("Content".equals(name)) {
+ String viewNames = XmlUtil.getAttribute(element, "view", "default");
+ for (String view : viewNames.split(",")) {
+ view = view.trim();
+ List<Element> viewElements = views.get(view);
+ if (viewElements == null) {
+ viewElements = new LinkedList<Element>();
+ views.put(view, viewElements);
+ }
+ viewElements.add(element);
+ }
+ }
+ }
+
+ if (modulePrefs == null) {
+ throw new SpecParserException(
+ "At least 1 ModulePrefs is required.");
+ } else {
+ this.modulePrefs = modulePrefs;
+ }
+
+ if (views.size() == 0) {
+ throw new SpecParserException("At least 1 Content is required.");
+ } else {
+ Map<String, View> tmpViews = new HashMap<String, View>();
+ for (Map.Entry<String, List<Element>> view : views.entrySet()) {
+ View v = new View(view.getKey(), view.getValue());
+ tmpViews.put(v.getName(), v);
+ }
+ this.views = Collections.unmodifiableMap(tmpViews);
+ }
+
+ if (userPrefs.size() > 0) {
+ this.userPrefs = Collections.unmodifiableList(userPrefs);
+ } else {
+ this.userPrefs = Collections.emptyList();
+ }
+ }
+
+ /**
+ * Constructs a GadgetSpec for substitute calls.
+ * @param spec
+ */
+ private GadgetSpec(GadgetSpec spec) {
+ url = spec.url;
+ checksum = spec.checksum;
+ }
+}
\ No newline at end of file
Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/Icon.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/Icon.java?rev=635862&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/Icon.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/Icon.java Tue Mar 11 02:52:52 2008
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.shindig.gadgets.spec;
+import org.apache.shindig.gadgets.Substitutions;
+import org.apache.shindig.util.XmlUtil;
+
+import org.w3c.dom.Element;
+
+/**
+ * Represents a ModuleSpec.Icon tag.
+ *
+ * TODO: Support substitution
+ */
+public class Icon {
+ /**
+ * Icon@mode
+ * Probably better labeled "encoding"; currently only base64 is supported.
+ * If mode is not set, content must be a url. Otherwise, content is
+ * a mode-encoded image with a mime type equal to type.
+ */
+ private final String mode;
+ public String getMode() {
+ return mode;
+ }
+
+ /**
+ * Icon@type
+ * Mime type of the icon
+ */
+ private final String type;
+ public String getType() {
+ return type;
+ }
+
+ /**
+ * Icon#CDATA
+ *
+ * Message Bundles
+ */
+ private String content;
+ public String getContent() {
+ return content;
+ }
+
+ /**
+ * Substitutes the icon fields according to the spec.
+ *
+ * @param substituter
+ * @return The substituted icon
+ */
+ public Icon substitute(Substitutions substituter) {
+ Icon icon = new Icon(this);
+ icon.content
+ = substituter.substituteString(Substitutions.Type.MESSAGE, content);
+ return icon;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<Icon type=\"")
+ .append(type)
+ .append("\" mode=\"")
+ .append(mode)
+ .append("\">")
+ .append(content)
+ .append("</Icon>");
+ return buf.toString();
+ }
+
+ /**
+ * Currently does not validate icon data.
+ * @param element
+ */
+ public Icon(Element element) throws SpecParserException {
+ mode = XmlUtil.getAttribute(element, "mode");
+ if (mode != null && !mode.equals("base64")) {
+ throw new SpecParserException(
+ "The only valid value for Icon@mode is \"base64\"");
+ }
+ type = XmlUtil.getAttribute(element, "type", "");
+ content = element.getTextContent();
+ }
+
+ /**
+ * Creates an icon for substitute()
+ *
+ * @param icon
+ */
+ private Icon(Icon icon) {
+ mode = icon.mode;
+ type = icon.type;
+ }
+}
\ No newline at end of file
Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/LocaleSpec.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/LocaleSpec.java?rev=635862&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/LocaleSpec.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/LocaleSpec.java Tue Mar 11 02:52:52 2008
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.shindig.gadgets.spec;
+import org.apache.shindig.util.XmlUtil;
+
+import org.w3c.dom.Element;
+
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+/**
+ * Represents a Locale tag.
+ * Generally compatible with java.util.Locale, but with some extra
+ * localization data from the spec.
+ * Named "LocaleSpec" so as to not conflict with java.util.Locale
+ *
+ * No localization.
+ * No user pref substitution.
+ */
+public class LocaleSpec {
+
+ /**
+ * Locale@lang
+ */
+ private final String language;
+ public String getLanguage() {
+ return language;
+ }
+
+ /**
+ * Locale@country
+ */
+ private final String country;
+ public String getCountry() {
+ return country;
+ }
+
+ /**
+ * Locale@language_direction
+ */
+ private final String languageDirection;
+ public String getLanguageDirection() {
+ return languageDirection;
+ }
+
+ /**
+ * Locale@messages
+ */
+ private final URI messages;
+ public URI getMessages() {
+ return messages;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<Locale lang=\"")
+ .append(language)
+ .append("\" country=\"")
+ .append(country)
+ .append("\" language_direction=\"")
+ .append(languageDirection)
+ .append("\" messages=\"")
+ .append(messages)
+ .append("\"/>");
+ return buf.toString();
+ }
+
+ /**
+ * @param element
+ * @param specUrl The url that the spec is loaded from. messages is assumed
+ * to be relative to this path.
+ * @throws SpecParserException If language_direction is not valid
+ */
+ public LocaleSpec(Element element, URI specUrl) throws SpecParserException {
+ language = XmlUtil.getAttribute(element, "lang", "all").toLowerCase();
+ country = XmlUtil.getAttribute(element, "country", "ALL").toUpperCase();
+ languageDirection
+ = XmlUtil.getAttribute(element, "language_direction", "ltr");
+ if (!("ltr".equals(languageDirection) ||
+ "rtl".equals(languageDirection))) {
+ throw new SpecParserException(
+ "Locale@language_direction must be ltr or rtl");
+ }
+ String messages = XmlUtil.getAttribute(element, "messages");
+ if (messages == null) {
+ this.messages = URI.create("");
+ } else {
+ try {
+ this.messages = new URL(specUrl.toURL(), messages).toURI();
+ } catch (URISyntaxException e) {
+ throw new SpecParserException("Locale@messages url is invalid.");
+ } catch (MalformedURLException e) {
+ throw new SpecParserException("Locale@messages url is invalid.");
+ }
+ }
+ }
+}
Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/MessageBundle.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/MessageBundle.java?rev=635862&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/MessageBundle.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/MessageBundle.java Tue Mar 11 02:52:52 2008
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.shindig.gadgets.spec;
+
+import org.apache.shindig.util.XmlException;
+import org.apache.shindig.util.XmlUtil;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Represents a messagebundle structure.
+ */
+public class MessageBundle {
+
+ public static final MessageBundle EMPTY = new MessageBundle();
+
+ private final Map<String, String> messages;
+ /**
+ * @return A read-only view of the message bundle.
+ */
+ public Map<String, String> getMessages() {
+ return messages;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<messagebundle>\n");
+ for (Map.Entry<String, String> entry : messages.entrySet()) {
+ buf.append("<msg name=\"").append(entry.getKey()).append("\">")
+ .append(entry.getValue())
+ .append("</msg>\n");
+ }
+ buf.append("</messagebundle>");
+ return buf.toString();
+ }
+
+ /**
+ * Constructs a message bundle from input xml
+ * @param xml
+ * @throws SpecParserException
+ */
+ public MessageBundle(String xml) throws SpecParserException {
+ Element doc;
+ try {
+ doc = XmlUtil.parse(xml);
+ } catch (XmlException e) {
+ throw new SpecParserException(e);
+ }
+
+ Map<String, String> messages = new HashMap<String, String>();
+
+ NodeList nodes = doc.getElementsByTagName("msg");
+ for (int i = 0, j = nodes.getLength(); i < j; ++i) {
+ Element msg = (Element)nodes.item(i);
+ String name = XmlUtil.getAttribute(msg, "name");
+ if (name == null) {
+ throw new SpecParserException(
+ "All message bundle entries must have a name attribute.");
+ }
+ messages.put(name, msg.getTextContent());
+ }
+ this.messages = Collections.unmodifiableMap(messages);
+ }
+
+ private MessageBundle() {
+ this.messages = Collections.emptyMap();
+ }
+}
Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/ModulePrefs.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/ModulePrefs.java?rev=635862&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/ModulePrefs.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/ModulePrefs.java Tue Mar 11 02:52:52 2008
@@ -0,0 +1,437 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.shindig.gadgets.spec;
+import org.apache.shindig.gadgets.Substitutions;
+import org.apache.shindig.util.XmlUtil;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Represents the ModulePrefs element of a gadget spec.
+ *
+ * This encapsulates most gadget meta data, including everything except for
+ * Content and UserPref nodes.
+ */
+public class ModulePrefs {
+ // Canonical spec items first.
+
+ /**
+ * ModulePrefs@title
+ *
+ * User Pref + Message Bundle + Bidi
+ */
+ private String title;
+ public String getTitle() {
+ return title;
+ }
+
+ /**
+ * ModulePrefs@title_url
+ *
+ * User Pref + Message Bundle + Bidi
+ */
+ private URI titleUrl;
+ public URI getTitleUrl() {
+ return titleUrl;
+ }
+
+ /**
+ * ModulePrefs@description
+ *
+ * Message Bundles
+ */
+ private String description;
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * ModulePrefs@author
+ *
+ * Message Bundles
+ */
+ private String author;
+ public String getAuthor() {
+ return author;
+ }
+
+ /**
+ * ModulePrefs@author_email
+ *
+ * Message Bundles
+ */
+ private String authorEmail;
+ public String getAuthorEmail() {
+ return authorEmail;
+ }
+
+ /**
+ * ModulePrefs@screenshot
+ *
+ * Message Bundles
+ */
+ private URI screenshot;
+ public URI getScreenshot() {
+ return screenshot;
+ }
+
+ /**
+ * ModulePrefs@thumbnail
+ *
+ * Message Bundles
+ */
+ private URI thumbnail;
+ public URI getThumbnail() {
+ return thumbnail;
+ }
+
+ // Extended data (typically used by directories)
+
+ /**
+ * ModulePrefs@directory_title
+ *
+ * Message Bundles
+ */
+ private String directoryTitle;
+ public String getDirectoryTitle() {
+ return directoryTitle;
+ }
+
+ /*
+ * The following ModulePrefs attributes are skipped:
+ *
+ * author_affiliation
+ * author_location
+ * author_photo
+ * author_aboutme
+ * author_quote
+ * author_link
+ * show_stats
+ * show_in_directory
+ */
+
+ /**
+ * ModuleSpec@width
+ */
+ private final int width;
+ public int getWidth() {
+ return width;
+ }
+
+ /**
+ * ModuleSpec@width
+ */
+ private final int height;
+ public int getHeight() {
+ return height;
+ }
+
+ /**
+ * ModuleSpec@category
+ * ModuleSpec@category2
+ * These fields are flattened into a single list.
+ */
+ private final List<String> categories;
+ public List<String> getCategories() {
+ return categories;
+ }
+
+ /**
+ * Skipped:
+ *
+ * singleton
+ * render_inline
+ * scaling
+ * scrolling
+ */
+
+ /**
+ * ModuleSpec.Require
+ * ModuleSpec.Optional
+ */
+ private final Map<String, Feature> features;
+ public Map<String, Feature> getFeatures() {
+ return features;
+ }
+
+ /**
+ * ModuleSpec.Preload
+ */
+ private final List<URI> preloads;
+ public List<URI> getPreloads() {
+ return preloads;
+ }
+
+ /**
+ * ModuleSpec.Icon
+ */
+ private List<Icon> icons;
+ public List<Icon> getIcons() {
+ return icons;
+ }
+
+ /**
+ * ModuleSpec.Locale
+ */
+ private final Map<Locale, LocaleSpec> locales;
+ public Map<Locale, LocaleSpec> getLocales() {
+ return locales;
+ }
+
+ /**
+ * Attempts to retrieve a valid LocaleSpec for the given Locale.
+ * First tries to find an exact language / country match.
+ * Then tries to find a match for language / all.
+ * Then tries to find a match for all / all.
+ * Finally gives up.
+ * @param locale
+ * @return The locale spec, if there is a matching one, or null.
+ */
+ public LocaleSpec getLocale(Locale locale) {
+ if (locales.size() == 0) {
+ return null;
+ }
+ LocaleSpec localeSpec = locales.get(locale);
+ if (localeSpec == null) {
+ locale = new Locale(locale.getLanguage(), "ALL");
+ localeSpec = locales.get(locale);
+ if (localeSpec == null) {
+ localeSpec = locales.get(GadgetSpec.DEFAULT_LOCALE);
+ }
+ }
+
+ return localeSpec;
+ }
+
+ /**
+ * Produces a new ModulePrefs by substituting hangman variables from
+ * substituter. See comments on individual fields to see what actually
+ * has substitutions performed.
+ *
+ * @param substituter
+ */
+ public ModulePrefs substitute(Substitutions substituter) {
+ ModulePrefs prefs = new ModulePrefs(this);
+
+ // Icons, if any
+ if (icons.size() == 0) {
+ prefs.icons = Collections.emptyList();
+ } else {
+ List<Icon> iconList = new ArrayList<Icon>(icons.size());
+ for (Icon icon : icons) {
+ iconList.add(icon.substitute(substituter));
+ }
+ prefs.icons = Collections.unmodifiableList(iconList);
+ }
+
+ Substitutions.Type type = Substitutions.Type.MESSAGE;
+ // Most attributes only get strings.
+ prefs.author = substituter.substituteString(type, author);
+ prefs.authorEmail = substituter.substituteString(type, authorEmail);
+ prefs.description = substituter.substituteString(type, description);
+ prefs.directoryTitle = substituter.substituteString(type, directoryTitle);
+ prefs.screenshot = substituter.substituteUri(type, screenshot);
+ prefs.thumbnail = substituter.substituteUri(type, thumbnail);
+
+ // All types.
+ prefs.title = substituter.substituteString(null, title);
+ prefs.titleUrl = substituter.substituteUri(null, titleUrl);
+ return prefs;
+ }
+
+
+ /**
+ * Walks child nodes of the given node.
+ * @param element
+ * @param visitors Map of tag names to visitors for that tag.
+ */
+ private static void walk(Element element, Map<String, ElementVisitor> visitors)
+ throws SpecParserException {
+ NodeList children = element.getChildNodes();
+ for (int i = 0, j = children.getLength(); i < j; ++i) {
+ Node child = children.item(i);
+ ElementVisitor visitor = visitors.get(child.getNodeName());
+ if (visitor != null) {
+ visitor.visit((Element)child);
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<ModulePrefs")
+ .append(" title=\"").append(title).append("\"")
+ .append(" author=\"").append(author).append("\"")
+ .append(" author_email=\"").append(authorEmail).append("\"")
+ .append(" description=\"").append(description).append("\"")
+ .append(" directory_title=\"").append(directoryTitle).append("\"")
+ .append(" screenshot=\"").append(screenshot).append("\"")
+ .append(" thumbnail=\"").append(thumbnail).append("\"")
+ .append(" height=\"").append(height).append("\"")
+ .append(" width=\"").append(width).append("\"")
+ .append(" category1=\"").append(categories.get(0)).append("\"")
+ .append(" category2=\"").append(categories.get(1)).append("\"")
+ .append(">\n");
+ for (URI preload : preloads) {
+ buf.append("<Preload href=\"").append(preload).append("\"/>\n");
+ }
+ for (Feature feature : features.values()) {
+ buf.append(feature).append("\n");
+ }
+ for (Icon icon : icons) {
+ buf.append(icon).append("\n");
+ }
+ for (LocaleSpec locale : locales.values()) {
+ buf.append(locale).append("\n");
+ }
+ buf.append("</ModulePrefs>");
+ return buf.toString();
+ }
+
+ /**
+ * @param element
+ * @param specUrl
+ */
+ public ModulePrefs(Element element, URI specUrl) throws SpecParserException {
+ title = XmlUtil.getAttribute(element, "title");
+ if (title == null) {
+ throw new SpecParserException("ModulePrefs@title is required.");
+ }
+ URI emptyUri = URI.create("");
+ titleUrl = XmlUtil.getUriAttribute(element, "title_url", emptyUri);
+ author = XmlUtil.getAttribute(element, "author", "");
+ authorEmail = XmlUtil.getAttribute(element, "author_email", "");
+ description = XmlUtil.getAttribute(element, "description", "");
+ directoryTitle = XmlUtil.getAttribute(element, "directory_title", "");
+ screenshot = XmlUtil.getUriAttribute(element, "screenshot", emptyUri);
+ thumbnail = XmlUtil.getUriAttribute(element, "thumbnail", emptyUri);
+
+ String height = XmlUtil.getAttribute(element, "height");
+ if (height == null) {
+ this.height = 0;
+ } else {
+ this.height = Integer.parseInt(height);
+ }
+ String width = XmlUtil.getAttribute(element, "width");
+ if (width == null) {
+ this.width = 0;
+ } else {
+ this.width = Integer.parseInt(width);
+ }
+ categories = Arrays.asList(
+ XmlUtil.getAttribute(element, "category1", ""),
+ XmlUtil.getAttribute(element, "category2", ""));
+
+ // Child elements
+ PreloadVisitor preloadVisitor = new PreloadVisitor();
+ FeatureVisitor featureVisitor = new FeatureVisitor();
+ IconVisitor iconVisitor = new IconVisitor();
+ LocaleVisitor localeVisitor = new LocaleVisitor(specUrl);
+ Map<String, ElementVisitor> visitors = new HashMap<String, ElementVisitor>();
+ visitors.put("Preload", preloadVisitor);
+ visitors.put("Optional", featureVisitor);
+ visitors.put("Require", featureVisitor);
+ visitors.put("Icon", iconVisitor);
+ visitors.put("Locale", localeVisitor);
+ walk(element, visitors);
+ preloads = Collections.unmodifiableList(preloadVisitor.preloads);
+ features = Collections.unmodifiableMap(featureVisitor.features);
+ icons = Collections.unmodifiableList(iconVisitor.icons);
+ locales = Collections.unmodifiableMap(localeVisitor.locales);
+ }
+
+ /**
+ * Creates an empty module prefs for substitute() to use.
+ */
+ private ModulePrefs(ModulePrefs prefs) {
+ categories = prefs.getCategories();
+ preloads = prefs.getPreloads();
+ features = prefs.getFeatures();
+ locales = prefs.getLocales();
+ height = prefs.getHeight();
+ width = prefs.getWidth();
+ }
+}
+
+interface ElementVisitor {
+ public void visit(Element element) throws SpecParserException;
+}
+
+/**
+ * Processes ModulePrefs.Preload into a list.
+ */
+class PreloadVisitor implements ElementVisitor {
+ final List<URI> preloads = new LinkedList<URI>();
+ public void visit(Element element) throws SpecParserException {
+ URI href = XmlUtil.getUriAttribute(element, "href");
+ if (href == null) {
+ throw new SpecParserException("Preload@href is required.");
+ }
+ preloads.add(href);
+ }
+}
+
+/**
+ * Processes ModulePrefs.Require and ModulePrefs.Optional
+ */
+class FeatureVisitor implements ElementVisitor {
+ final Map<String, Feature> features = new HashMap<String, Feature>();
+ public void visit (Element element) throws SpecParserException {
+ Feature feature = new Feature(element);
+ features.put(feature.getName(), feature);
+ }
+}
+
+/**
+ * Processes ModulePrefs.Icon
+ */
+class IconVisitor implements ElementVisitor {
+ final List<Icon> icons = new LinkedList<Icon>();
+ public void visit(Element element) throws SpecParserException {
+ icons.add(new Icon(element));
+ }
+}
+
+/**
+ * Process ModulePrefs.Locale
+ */
+class LocaleVisitor implements ElementVisitor {
+ final URI base;
+ final Map<Locale, LocaleSpec> locales
+ = new HashMap<Locale, LocaleSpec>();
+ public void visit(Element element) throws SpecParserException {
+ LocaleSpec locale = new LocaleSpec(element, base);
+ locales.put(new Locale(locale.getLanguage(), locale.getCountry()), locale);
+ }
+ public LocaleVisitor(URI base) {
+ this.base = base;
+ }
+}
Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/SpecParserException.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/SpecParserException.java?rev=635862&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/SpecParserException.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/SpecParserException.java Tue Mar 11 02:52:52 2008
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.shindig.gadgets.spec;
+
+import org.apache.shindig.gadgets.GadgetException;
+import org.apache.shindig.util.XmlException;
+
+/**
+ * Exceptions for Gadget Spec parsing.
+ */
+public class SpecParserException extends GadgetException {
+ /**
+ * @param message
+ */
+ public SpecParserException(String message) {
+ super(GadgetException.Code.MALFORMED_XML_DOCUMENT, message);
+ }
+
+ public SpecParserException(XmlException e) {
+ super(GadgetException.Code.MALFORMED_XML_DOCUMENT, e);
+ }
+}
Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/UserPref.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/UserPref.java?rev=635862&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/UserPref.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/UserPref.java Tue Mar 11 02:52:52 2008
@@ -0,0 +1,215 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.shindig.gadgets.spec;
+import org.apache.shindig.gadgets.Substitutions;
+import org.apache.shindig.util.XmlUtil;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Represents a UserPref tag.
+ */
+public class UserPref {
+ /**
+ * UserPref@name
+ * Message bundles
+ */
+ private String name;
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * UserPref@display_name
+ * Message bundles
+ */
+ private String displayName;
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ /**
+ * UserPref@default_value
+ * Message bundles
+ */
+ private String defaultValue;
+ public String getDefaultValue() {
+ return defaultValue;
+ }
+
+ /**
+ * UserPref@required
+ */
+ private final boolean required;
+ public boolean getRequired() {
+ return required;
+ }
+
+ /**
+ * UserPref@datatype
+ */
+ private final DataType dataType;
+ public DataType getDataType() {
+ return dataType;
+ }
+
+ /**
+ * UserPref.EnumValue
+ * Collapsed so that EnumValue@value is the key and EnumValue@display_value
+ * is the value. If display_value is not present, value will be used.
+ * Message bundles are substituted into display_value, but not value.
+ */
+ private Map<String, String> enumValues;
+ public Map<String, String> getEnumValues() {
+ return enumValues;
+ }
+
+ /**
+ * Performs substitutions on the pref. See field comments for details on what
+ * is substituted.
+ *
+ * @param substituter
+ * @return The substituted pref.
+ */
+ public UserPref substitute(Substitutions substituter) {
+ UserPref pref = new UserPref(this);
+ Substitutions.Type type = Substitutions.Type.MESSAGE;
+ pref.displayName = substituter.substituteString(type, displayName);
+ pref.defaultValue = substituter.substituteString(type, defaultValue);
+ if (enumValues.size() == 0) {
+ pref.enumValues = Collections.emptyMap();
+ } else {
+ Map<String, String> values
+ = new HashMap<String, String>(enumValues.size());
+ for (Map.Entry<String, String> entry : enumValues.entrySet()) {
+ values.put(entry.getKey(),
+ substituter.substituteString(type, entry.getValue()));
+ }
+ pref.enumValues = Collections.unmodifiableMap(values);
+ }
+ return pref;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<UserPref name=\"")
+ .append(name)
+ .append("\" display_name=\"")
+ .append(displayName)
+ .append("\" default_value=\"")
+ .append(defaultValue)
+ .append("\" required=\"")
+ .append(required)
+ .append("\" datatype=\"")
+ .append(dataType.toString().toLowerCase())
+ .append("\"");
+ if (enumValues.size() == 0) {
+ buf.append("/>");
+ } else {
+ buf.append("\n");
+ for (Map.Entry<String, String> entry : enumValues.entrySet()) {
+ buf.append("<EnumValue value=\"")
+ .append(entry.getKey())
+ .append("\" value=\"")
+ .append("\" display_value=\"")
+ .append(entry.getValue())
+ .append("\"/>\n");
+ }
+ buf.append("</UserPref>");
+ }
+ return buf.toString();
+ }
+
+ /**
+ * @param element
+ * @throws SpecParserException
+ */
+ public UserPref(Element element) throws SpecParserException {
+ String name = XmlUtil.getAttribute(element, "name");
+ if (name == null) {
+ throw new SpecParserException("UserPref@name is required.");
+ }
+ this.name = name;
+
+ displayName = XmlUtil.getAttribute(element, "display_name", name);
+ defaultValue = XmlUtil.getAttribute(element, "default_value", "");
+ required = XmlUtil.getBoolAttribute(element, "required");
+
+ String dataType = XmlUtil.getAttribute(element, "datatype");
+ if (dataType == null) {
+ throw new SpecParserException("UserPref@datatype is required.");
+ }
+ this.dataType = DataType.parse(dataType);
+
+ NodeList children = element.getElementsByTagName("EnumValue");
+ if (children.getLength() > 0) {
+ Map<String, String> enumValues = new HashMap<String, String>();
+ for (int i = 0, j = children.getLength(); i < j; ++i) {
+ Element child = (Element)children.item(i);
+ String value = XmlUtil.getAttribute(child, "value");
+ if (value == null) {
+ throw new SpecParserException("EnumValue@value is required.");
+ }
+ String displayValue
+ = XmlUtil.getAttribute(child, "display_value", value);
+ enumValues.put(value, displayValue);
+ }
+ this.enumValues = Collections.unmodifiableMap(enumValues);
+ } else {
+ this.enumValues = Collections.emptyMap();
+ }
+ }
+
+ /**
+ * Produces a UserPref suitable for substitute()
+ * @param userPref
+ */
+ private UserPref(UserPref userPref) {
+ name = userPref.name;
+ dataType = userPref.dataType;
+ required = userPref.required;
+ }
+
+ /**
+ * Possible values for UserPref@datatype
+ */
+ public static enum DataType {
+ STRING, HIDDEN, BOOL, ENUM, LIST, NUMBER;
+
+ /**
+ * Parses a data type from the input string.
+ *
+ * @param value
+ * @return The data type of the given value.
+ */
+ public static DataType parse(String value) {
+ for (DataType type : DataType.values()) {
+ if (type.toString().compareToIgnoreCase(value) == 0) {
+ return type;
+ }
+ }
+ return STRING;
+ }
+ }
+}