org.apache.shindig.gadgets.rewrite.ImageAttributeRewriter 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.rewrite;
import com.google.common.collect.Lists;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import org.apache.shindig.gadgets.http.RequestPipeline;
import org.apache.shindig.gadgets.http.HttpRequest;
import org.apache.shindig.gadgets.http.MultipleResourceHttpFetcher;
import org.apache.shindig.gadgets.http.MultipleResourceHttpFetcher.RequestContext;
import org.apache.shindig.gadgets.http.HttpResponse;
import org.apache.shindig.gadgets.Gadget;
import org.apache.shindig.gadgets.GadgetException;
import org.apache.shindig.common.xml.DomUtil;
import org.apache.shindig.common.logging.i18n.MessageKeys;
import org.apache.shindig.common.uri.Uri;
import org.apache.shindig.common.uri.UriBuilder;
import org.apache.sanselan.ImageFormat;
import org.apache.sanselan.Sanselan;
import org.apache.sanselan.ImageInfo;
import org.apache.sanselan.ImageReadException;
import org.apache.sanselan.common.byteSources.ByteSourceInputStream;
import org.w3c.dom.Node;
import org.w3c.dom.Element;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.FutureTask;
import java.util.List;
import java.util.Map;
import java.io.IOException;
/**
* Rewriter that adds height/width attributes to tags.
*/
public class ImageAttributeRewriter extends DomWalker.Rewriter {
//class name for logging purpose
private static final String classname = ImageAttributeRewriter.class.getName();
private static final Logger LOG = Logger.getLogger(classname,MessageKeys.MESSAGES);
@Inject
public ImageAttributeRewriter(RequestPipeline requestPipeline, ExecutorService executor) {
super(new ImageAttributeVisitor(requestPipeline, executor));
}
/**
* Visitor that injects height/width attributes for tags, if needed to
* reduce the page reflows.
*/
public static class ImageAttributeVisitor implements DomWalker.Visitor {
private final RequestPipeline requestPipeline;
private final ExecutorService executor;
private static final String IMG_ATTR_CLASS_NAME_PREFIX = "__shindig__image";
public ImageAttributeVisitor(RequestPipeline requestPipeline,
@Named("shindig.concat.executor") ExecutorService executor) {
this.requestPipeline = requestPipeline;
this.executor = executor;
}
public VisitStatus visit(Gadget gadget, Node node) throws RewritingException {
if (node.getNodeType() == Node.ELEMENT_NODE &&
node.getNodeName().equalsIgnoreCase("img")) {
Element imageElement = (Element) node;
// we process the tag when it does not have 'class' and 'id'
// attributes in order to avoid conflicts from css styles.
if ("".equals(imageElement.getAttribute("class")) &&
"".equals(imageElement.getAttribute("id")) &&
!"".equals(imageElement.getAttribute("src")) &&
"".equals(imageElement.getAttribute("height")) &&
"".equals(imageElement.getAttribute("width"))) {
return VisitStatus.RESERVE_NODE;
}
}
return VisitStatus.BYPASS;
}
public boolean revisit(Gadget gadget, List nodes) throws RewritingException {
if (nodes.isEmpty()) {
return false;
}
Node head = DomUtil.getFirstNamedChildNode(
nodes.get(0).getOwnerDocument().getDocumentElement(), "head");
if (head == null) {
// Should never occur; do for paranoia's sake.
return false;
}
List resourceRequests = Lists.newArrayList();
for (Node node : nodes) {
String imgSrc = ((Element) node).getAttribute("src");
Uri uri = UriBuilder.parse(imgSrc).toUri();
try {
resourceRequests.add(buildHttpRequest(gadget, uri));
} catch (GadgetException e) {
if (LOG.isLoggable(Level.WARNING)) {
LOG.logp(Level.WARNING, classname, "revisit", MessageKeys.UNABLE_TO_PROCESS_IMG, new Object[] {imgSrc});
}
}
}
MultipleResourceHttpFetcher fetcher =
new MultipleResourceHttpFetcher(requestPipeline, executor);
Map> futureTasks = fetcher.fetchUnique(resourceRequests);
String cssContent = processAllImgResources(nodes, futureTasks);
if (cssContent.length() > 0) {
Element style = nodes.get(0).getOwnerDocument().createElement("style");
style.setAttribute("type", "text/css");
style.setTextContent(cssContent);
head.insertBefore(style, head.getFirstChild());
}
return true;
}
/**
* The method process all the images, determine which of them are safe for
* injecting css styles for height/width extracted from the image metadata,
* and returns the string of css styles that needed to injected.
*
* @param nodes nodes list of nodes for this we want to height/width
* attribute injection in css.
* @param futureTasks futureTasks map of url -> futureTask for all the requests sent.
* @return string contianing the css styles that needs to be injected.
*/
private String processAllImgResources(List nodes,
Map> futureTasks) {
StringBuilder cssContent = new StringBuilder("");
for (int i = 0; i < nodes.size(); i++) {
Element imageElement = (Element) nodes.get(i);
String src = imageElement.getAttribute("src");
RequestContext requestCxt;
// Fetch the content of the requested uri.
try {
Uri imgUri = UriBuilder.parse(src).toUri();
try {
requestCxt = futureTasks.get(imgUri).get();
} catch (InterruptedException ie) {
throw new GadgetException(GadgetException.Code.INTERNAL_SERVER_ERROR, ie);
} catch (ExecutionException ie) {
throw new GadgetException(GadgetException.Code.INTERNAL_SERVER_ERROR, ie);
}
if (requestCxt.getGadgetException() != null) {
throw requestCxt.getGadgetException();
}
HttpResponse response = requestCxt.getHttpResp();
// Content header checking is fast so this is fine to do for every
// response.
ImageFormat imageFormat = Sanselan.guessFormat(
new ByteSourceInputStream(response.getResponse(), imgUri.getPath()));
if (imageFormat == ImageFormat.IMAGE_FORMAT_UNKNOWN) {
// skip this node
continue;
}
// extract height and width from the actual image and set these
// attributes of the tag.
ImageInfo imageInfo = Sanselan.getImageInfo(response.getResponse(),
imgUri.getPath());
if (imageInfo == null) {
continue;
}
int imageHeight = imageInfo.getHeight();
int imageWidth = imageInfo.getWidth();
if (imageHeight > 0 && imageWidth > 0 && imageHeight * imageWidth > 1) {
imageElement.setAttribute("class", IMG_ATTR_CLASS_NAME_PREFIX + i);
cssContent.append('.').append(IMG_ATTR_CLASS_NAME_PREFIX).append(i).append(" {\n")
.append(" height: ").append(imageHeight).append("px;\n")
.append(" width: ").append(imageWidth).append("px;\n")
.append("}\n");
}
} catch (ImageReadException e) {
if (LOG.isLoggable(Level.WARNING)) {
LOG.logp(Level.WARNING, classname, "processAllImgResources", MessageKeys.UNABLE_TO_READ_RESPONSE, new Object[] {src});
}
} catch (GadgetException e) {
if (LOG.isLoggable(Level.WARNING)) {
LOG.logp(Level.WARNING, classname, "processAllImgResources", MessageKeys.UNABLE_TO_FETCH_IMG, new Object[] {src});
}
} catch (IOException e) {
if (LOG.isLoggable(Level.WARNING)) {
LOG.logp(Level.WARNING, classname, "processAllImgResources", MessageKeys.UNABLE_TO_PARSE_IMG, new Object[] {src});
}
}
}
return cssContent.toString();
}
// TODO(satya): Need to pass the request parameters as well ?
public static HttpRequest buildHttpRequest(Gadget gadget, Uri imgUri)
throws GadgetException {
HttpRequest req = new HttpRequest(imgUri);
req.setFollowRedirects(true);
return req;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy