org.apache.shindig.gadgets.servlet.GadgetsHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of shindig-gadgets Show documentation
Show all versions of shindig-gadgets Show documentation
Renders gadgets, provides the gadget metadata service, and serves
all javascript required by the OpenSocial specification.
The newest version!
/*
* 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.servlet;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Inject;
import org.apache.shindig.common.uri.Uri;
import org.apache.shindig.common.uri.Uri.UriException;
import org.apache.shindig.gadgets.http.HttpResponse;
import org.apache.shindig.gadgets.process.ProcessingException;
import org.apache.shindig.gadgets.servlet.GadgetsHandlerApi.RenderingContext;
import org.apache.shindig.gadgets.spec.GadgetSpec;
import org.apache.shindig.gadgets.uri.UriCommon;
import org.apache.shindig.protocol.BaseRequestItem;
import org.apache.shindig.protocol.Operation;
import org.apache.shindig.protocol.ProtocolException;
import org.apache.shindig.protocol.RequestItem;
import org.apache.shindig.protocol.Service;
import org.apache.shindig.protocol.conversion.BeanDelegator;
import org.apache.shindig.protocol.conversion.BeanFilter;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletResponse;
/**
* Provides endpoints for gadget metadata lookup and more.
*
* @since 2.0.0
*/
@Service(name = "gadgets")
public class GadgetsHandler {
@VisibleForTesting
static final String FAILURE_METADATA = "Failed to get gadget metadata.";
@VisibleForTesting
static final String FAILURE_TOKEN = "Failed to get gadget token.";
@VisibleForTesting
static final String FAILURE_PROXY = "Failed to get proxy data.";
@VisibleForTesting
static final String FAILURE_CAJA = "Failed to cajole data.";
@VisibleForTesting
static final String FAILURE_JS = "Failed to get js data.";
private static final List DEFAULT_METADATA_FIELDS =
ImmutableList.of("iframeUrl", "userPrefs.*", "modulePrefs.*", "views.*");
private static final List DEFAULT_TOKEN_FIELDS = ImmutableList.of("token");
private static final List DEFAULT_PROXY_FIELDS = ImmutableList.of("proxyUrl");
private static final List DEFAULT_CAJA_FIELDS = ImmutableList.of("*");
private static final List DEFAULT_JS_FIELDS = ImmutableList.of("jsUrl");
private static final Logger LOG = Logger.getLogger(GadgetsHandler.class.getName());
/**
* Enum to list the used JSON/JSONP request parameters
* It mostly reference the UriCommon fields for consistency,
* This enum defined the API names, Do not change the names!
*/
enum Param {
IDS("ids"),
CONTAINER(UriCommon.Param.CONTAINER.getKey()),
FIELDS("fields"),
DEBUG(UriCommon.Param.DEBUG),
NO_CACHE(UriCommon.Param.NO_CACHE),
REFRESH(UriCommon.Param.REFRESH),
LANG(UriCommon.Param.LANG),
COUNTRY(UriCommon.Param.COUNTRY),
VIEW(UriCommon.Param.VIEW),
RENDER_TYPE("render"),
SANITIZE(UriCommon.Param.SANITIZE),
GADGET(UriCommon.Param.GADGET),
FALLBACK_URL(UriCommon.Param.FALLBACK_URL_PARAM),
REWRITE_MIME(UriCommon.Param.REWRITE_MIME_TYPE),
NO_EXPAND(UriCommon.Param.NO_EXPAND),
RESIZE_HEIGHT(UriCommon.Param.RESIZE_HEIGHT),
RESIZE_WIDTH(UriCommon.Param.RESIZE_WIDTH),
RESIZE_QUALITY(UriCommon.Param.RESIZE_QUALITY),
FEATURES("features"),
LOADED_FEATURES("loadedFeatures"),
CONTAINER_MODE(UriCommon.Param.CONTAINER_MODE),
ONLOAD(UriCommon.Param.ONLOAD),
REPOSITORY(UriCommon.Param.REPOSITORY_ID),
MIME_TYPE("mime_type");
private final String name;
Param(String name) { this.name = name; }
Param(UriCommon.Param param) { this.name = param.getKey(); }
String getName() { return name; }
}
protected final ExecutorService executor;
protected final GadgetsHandlerService handlerService;
protected final BeanFilter beanFilter;
protected final BeanDelegator beanDelegator;
@Inject
public GadgetsHandler(ExecutorService executor, GadgetsHandlerService handlerService,
BeanFilter beanFilter) {
this.executor = executor;
this.handlerService = handlerService;
this.beanFilter = beanFilter;
this.beanDelegator = new BeanDelegator();
}
@Operation(httpMethods = {"POST", "GET"}, path = "metadata")
public Map metadata(BaseRequestItem request)
throws ProtocolException {
return new AbstractExecutor() {
@Override
protected Callable createJob(String url, BaseRequestItem request)
throws ProcessingException {
return createMetadataJob(url, request);
}
}.execute(request);
}
@Operation(httpMethods = {"POST", "GET"}, path = "token")
public Map token(BaseRequestItem request)
throws ProtocolException {
return new AbstractExecutor() {
@Override
protected Callable createJob(String url, BaseRequestItem request)
throws ProcessingException {
return createTokenJob(url, request);
}
}.execute(request);
}
@Operation(httpMethods = {"POST", "GET"}, path = "js")
public GadgetsHandlerApi.BaseResponse js(BaseRequestItem request)
throws ProtocolException {
// No need for threading since it is one request
GadgetsHandlerApi.BaseResponse response;
try {
JsRequestData jsRequest = new JsRequestData(request);
response = handlerService.getJs(jsRequest);
} catch (ProcessingException e) {
response = handlerService.createErrorResponse(null, e.getHttpStatusCode(), e.getMessage());
} catch (Exception e) {
LOG.log(Level.INFO, "Error fetching JS", e);
response = handlerService.createErrorResponse(null, HttpResponse.SC_INTERNAL_SERVER_ERROR,
FAILURE_JS);
}
return response;
}
@Operation(httpMethods = {"POST", "GET"}, path = "proxy")
public Map proxy(BaseRequestItem request)
throws ProtocolException {
return new AbstractExecutor() {
@Override
protected Callable createJob(String url, BaseRequestItem request)
throws ProcessingException {
return createProxyJob(url, request);
}
}.execute(request);
}
@Operation(httpMethods = {"POST", "GET"}, path = "cajole")
public Map cajole(BaseRequestItem request)
throws ProtocolException {
return new AbstractExecutor() {
@Override
protected Callable createJob(String url, BaseRequestItem request)
throws ProcessingException {
return createCajaJob(url, request);
}
}.execute(request);
}
@Operation(httpMethods = "GET", path = "/@metadata.supportedFields")
public Set supportedFields(RequestItem request) {
return ImmutableSet.copyOf(beanFilter
.getBeanFields(GadgetsHandlerApi.MetadataResponse.class, 5));
}
@Operation(httpMethods = "GET", path = "/@token.supportedFields")
public Set tokenSupportedFields(RequestItem request) {
return ImmutableSet.copyOf(
beanFilter.getBeanFields(GadgetsHandlerApi.TokenResponse.class, 5));
}
@Operation(httpMethods = "GET", path = "/@js.supportedFields")
public Set jsSupportedFields(RequestItem request) {
return ImmutableSet.copyOf(
beanFilter.getBeanFields(GadgetsHandlerApi.JsResponse.class, 5));
}
@Operation(httpMethods = "GET", path = "/@proxy.supportedFields")
public Set proxySupportedFields(RequestItem request) {
return ImmutableSet.copyOf(
beanFilter.getBeanFields(GadgetsHandlerApi.ProxyResponse.class, 5));
}
@Operation(httpMethods = "GET", path = "/@cajole.supportedFields")
public Set cajaSupportedFields(RequestItem request) {
return ImmutableSet.copyOf(beanFilter
.getBeanFields(GadgetsHandlerApi.CajaResponse.class, 5));
}
/**
* Class to handle threaded reply.
* Mainly it made to support filtering the id (url)
*/
class CallableData {
private final String id;
private final GadgetsHandlerApi.BaseResponse data;
public CallableData(String id, GadgetsHandlerApi.BaseResponse data) {
this.id = id;
this.data = data;
}
public String getId() { return id; }
public GadgetsHandlerApi.BaseResponse getData() { return data; }
}
private abstract class AbstractExecutor {
public Map execute(BaseRequestItem request) {
Set gadgetUrls = ImmutableSet.copyOf(request.getListParameter(Param.IDS.getName()));
if (gadgetUrls.isEmpty()) {
return ImmutableMap.of();
}
if (Strings.isNullOrEmpty(request.getParameter(Param.CONTAINER.getName()))) {
throw new ProtocolException(HttpServletResponse.SC_BAD_REQUEST,
"Missing container for request.");
}
ImmutableMap.Builder builder = ImmutableMap.builder();
int badReq = 0;
CompletionService completionService =
new ExecutorCompletionService(executor);
for (String gadgetUrl : gadgetUrls) {
try {
Callable job = createJob(gadgetUrl, request);
completionService.submit(job);
} catch (ProcessingException e) {
// Fail to create and submit job
builder.put(gadgetUrl, handlerService.createErrorResponse(null,
e.getHttpStatusCode(), e.getMessage()));
badReq++;
}
}
for (int numJobs = gadgetUrls.size() - badReq; numJobs > 0; numJobs--) {
CallableData response;
try {
response = completionService.take().get();
builder.put(response.getId(), response.getData());
} catch (InterruptedException e) {
throw new ProtocolException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Processing interrupted.", e);
} catch (ExecutionException e) {
throw new ProtocolException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Processing error.", e);
}
}
return builder.build();
}
protected abstract Callable createJob(String url, BaseRequestItem request)
throws ProcessingException;
}
// Hook to override in sub-class.
protected Callable createMetadataJob(final String url,
BaseRequestItem request) throws ProcessingException {
final MetadataRequestData metadataRequest = new MetadataRequestData(url, request);
return new Callable() {
public CallableData call() throws Exception {
try {
return new CallableData(url, handlerService.getMetadata(metadataRequest));
} catch (Exception e) {
return new CallableData(url,
handlerService.createErrorResponse(null, e, FAILURE_METADATA));
}
}
};
}
// Hook to override in sub-class.
protected Callable createTokenJob(final String url,
BaseRequestItem request) throws ProcessingException {
// TODO: Get token duration from requests
final TokenRequestData tokenRequest = new TokenRequestData(url, request, null);
return new Callable() {
public CallableData call() throws Exception {
try {
return new CallableData(url, handlerService.getToken(tokenRequest));
} catch (Exception e) {
return new CallableData(url,
handlerService.createErrorResponse(null, e, FAILURE_TOKEN));
}
}
};
}
// Hook to override in sub-class.
protected Callable createProxyJob(final String url,
BaseRequestItem request) throws ProcessingException {
final ProxyRequestData proxyRequest = new ProxyRequestData(url, request);
return new Callable() {
public CallableData call() throws Exception {
try {
return new CallableData(url, handlerService.getProxy(proxyRequest));
} catch (Exception e) {
return new CallableData(url,
handlerService.createErrorResponse(null, e, FAILURE_PROXY));
}
}
};
}
// Hook to override in sub-class.
protected Callable createCajaJob(final String url,
BaseRequestItem request) throws ProcessingException {
final CajaRequestData cajaRequest = new CajaRequestData(url, request);
return new Callable() {
public CallableData call() throws Exception {
try {
return new CallableData(url, handlerService.getCaja(cajaRequest));
} catch (Exception e) {
return new CallableData(url,
handlerService.createErrorResponse(null, e, FAILURE_CAJA));
}
}
};
}
/**
* Gadget context classes used to translate JSON BaseRequestItem into a more
* meaningful model objects that Java can work with.
*/
private abstract class AbstractRequest implements GadgetsHandlerApi.BaseRequest {
protected final Uri uri;
protected final String container;
protected final List fields;
protected final BaseRequestItem request;
public AbstractRequest(String url, BaseRequestItem request, List defaultFields)
throws ProcessingException {
try {
this.uri = (url != null ? Uri.parse(url) : null);
} catch (UriException e) {
throw new ProcessingException("Bad url - " + url, HttpServletResponse.SC_BAD_REQUEST);
}
this.request = request;
this.container = request.getParameter(Param.CONTAINER.getName());
this.fields = processFields(request, defaultFields);
}
protected String getParam(BaseRequestItem request, Param field) {
return request.getParameter(field.getName());
}
protected String getParam(BaseRequestItem request, Param field, String defaultValue) {
return request.getParameter(field.getName(), defaultValue);
}
protected List getListParam(BaseRequestItem request, Param field) {
return request.getListParameter(field.getName());
}
protected Boolean getBooleanParam(BaseRequestItem request, Param field) {
String val = request.getParameter(field.getName());
if (val != null) {
return "1".equals(val) || Boolean.valueOf(val);
}
return false;
}
protected Integer getIntegerParam(BaseRequestItem request, Param field)
throws ProcessingException {
String val = request.getParameter(field.getName());
Integer intVal = null;
if (val != null) {
try {
intVal = Integer.valueOf(val);
} catch (NumberFormatException e) {
throw new ProcessingException("Error parsing " + field + " parameter",
HttpServletResponse.SC_BAD_REQUEST);
}
}
return intVal;
}
public Uri getUrl() {
return uri;
}
public String getContainer() {
return container;
}
public List getFields() {
return fields;
}
private List processFields(BaseRequestItem request, List defaultList) {
List value = request.getListParameter(BaseRequestItem.FIELDS);
return ((value == null || value.isEmpty()) ? defaultList : value);
}
}
protected class JsRequestData extends AbstractRequest implements GadgetsHandlerApi.JsRequest {
private final Integer refresh;
private final boolean debug;
private final boolean ignoreCache;
private final List features;
private final List loadedFeatures;
private final RenderingContext context;
private final String onload;
private final String gadget;
private final String repository;
public JsRequestData(BaseRequestItem request) throws ProcessingException {
super(null, request, DEFAULT_JS_FIELDS);
this.ignoreCache = getBooleanParam(request, Param.NO_CACHE);
this.debug = getBooleanParam(request, Param.DEBUG);
this.refresh = getIntegerParam(request, Param.REFRESH);
this.features = getListParam(request, Param.FEATURES);
this.loadedFeatures = getListParam(request, Param.LOADED_FEATURES);
this.context = getRenderingContext(getParam(request, Param.CONTAINER_MODE));
this.onload = getParam(request, Param.ONLOAD);
this.gadget = getParam(request, Param.GADGET);
this.repository = getParam(request, Param.REPOSITORY);
}
public RenderingContext getContext() { return context; }
public boolean getDebug() { return debug; }
public List getFeatures() { return features; }
public List getLoadedFeatures() { return loadedFeatures; }
public boolean getIgnoreCache() { return ignoreCache; }
public String getOnload() { return onload; }
public Integer getRefresh() { return refresh; }
public String getGadget() { return gadget; }
public String getRepository() { return repository; }
}
private RenderingContext getRenderingContext(String param) {
RenderingContext context = RenderingContext.GADGET;
if ("1".equals(param)) {
context = RenderingContext.CONTAINER;
} else if ("2".equals(param)) {
context = RenderingContext.CONFIGURED_GADGET;
}
return context;
}
protected class ProxyRequestData extends AbstractRequest
implements GadgetsHandlerApi.ProxyRequest {
private final String gadget;
private final Integer refresh;
private final boolean debug;
private final boolean ignoreCache;
private final String fallbackUrl;
private final String mimetype;
private final boolean sanitize;
private final GadgetsHandlerApi.ImageParams imageParams;
public ProxyRequestData(String url, BaseRequestItem request) throws ProcessingException {
super(url, request, DEFAULT_PROXY_FIELDS);
this.ignoreCache = getBooleanParam(request, Param.NO_CACHE);
this.debug = getBooleanParam(request, Param.DEBUG);
this.sanitize = getBooleanParam(request, Param.SANITIZE);
this.gadget = getParam(request, Param.GADGET);
this.fallbackUrl = getParam(request, Param.FALLBACK_URL);
this.mimetype = getParam(request, Param.REWRITE_MIME);
this.refresh = getIntegerParam(request, Param.REFRESH);
imageParams = getImageParams(request);
}
private GadgetsHandlerApi.ImageParams getImageParams(BaseRequestItem request)
throws ProcessingException {
GadgetsHandlerApi.ImageParams params = null;
Boolean doNotExpand = getBooleanParam(request, Param.NO_EXPAND);
Integer height = getIntegerParam(request, Param.RESIZE_HEIGHT);
Integer width = getIntegerParam(request, Param.RESIZE_WIDTH);
Integer quality = getIntegerParam(request, Param.RESIZE_QUALITY);
if (height != null || width != null) {
return beanDelegator.createDelegator(null, GadgetsHandlerApi.ImageParams.class,
ImmutableMap.of(
"height", BeanDelegator.nullable(height),
"width", BeanDelegator.nullable(width),
"quality", BeanDelegator.nullable(quality),
"donotexpand", BeanDelegator.nullable(doNotExpand)));
}
return params;
}
public boolean getDebug() {
return debug;
}
public String getFallbackUrl() {
return fallbackUrl;
}
public boolean getIgnoreCache() {
return ignoreCache;
}
public GadgetsHandlerApi.ImageParams getImageParams() {
return imageParams;
}
public Integer getRefresh() {
return refresh;
}
public String getRewriteMimeType() {
return mimetype;
}
public boolean getSanitize() {
return sanitize;
}
public String getGadget() {
return gadget;
}
}
protected class TokenRequestData extends AbstractRequest
implements GadgetsHandlerApi.TokenRequest {
public TokenRequestData(String url, BaseRequestItem request, Long durationSeconds)
throws ProcessingException {
super(url, request, DEFAULT_TOKEN_FIELDS);
}
public GadgetsHandlerApi.AuthContext getAuthContext() {
return beanDelegator.createDelegator(
request.getToken(), GadgetsHandlerApi.AuthContext.class);
}
}
@VisibleForTesting
static GadgetsHandlerApi.RenderingType getRenderingType(String value)
throws ProcessingException {
GadgetsHandlerApi.RenderingType type = GadgetsHandlerApi.RenderingType.DEFAULT;
if (value != null) {
try {
type = GadgetsHandlerApi.RenderingType.valueOf(value.toUpperCase());
} catch (IllegalArgumentException e) {
throw new ProcessingException("Error parsing rendering type parameter",
HttpServletResponse.SC_BAD_REQUEST);
}
}
return type;
}
protected class CajaRequestData extends AbstractRequest
implements GadgetsHandlerApi.CajaRequest {
private final String mimeType;
private final boolean debug;
public CajaRequestData(String url, BaseRequestItem request)
throws ProcessingException {
super(url, request, DEFAULT_CAJA_FIELDS);
this.mimeType = getParam(request, Param.MIME_TYPE, "text/html");
this.debug = getBooleanParam(request, Param.DEBUG);
}
public String getMimeType() {
return mimeType;
}
public boolean getDebug() {
return debug;
}
}
protected class MetadataRequestData extends AbstractRequest
implements GadgetsHandlerApi.MetadataRequest {
protected final Locale locale;
protected final boolean ignoreCache;
protected final boolean debug;
protected final GadgetsHandlerApi.RenderingType renderingType;
public MetadataRequestData(String url, BaseRequestItem request)
throws ProcessingException {
super(url, request, DEFAULT_METADATA_FIELDS);
String lang = request.getParameter("language");
String country = request.getParameter("country");
this.locale =
(lang != null && country != null) ? new Locale(lang, country) : (lang != null)
? new Locale(lang) : GadgetSpec.DEFAULT_LOCALE;
this.ignoreCache = getBooleanParam(request, Param.NO_CACHE);
this.debug = getBooleanParam(request, Param.DEBUG);
this.renderingType = GadgetsHandler.getRenderingType(getParam(request, Param.RENDER_TYPE));
}
public int getModuleId() {
return 1; // TODO calculate?
}
public Locale getLocale() {
return locale;
}
public boolean getIgnoreCache() {
return ignoreCache;
}
public boolean getDebug() {
return debug;
}
public String getView() {
return getParam(request, Param.VIEW, "default");
}
public GadgetsHandlerApi.AuthContext getAuthContext() {
return beanDelegator.createDelegator(
request.getToken(), GadgetsHandlerApi.AuthContext.class);
}
public GadgetsHandlerApi.RenderingType getRenderingType() {
return renderingType;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy