001 package org.apache.tapestry.contrib.services.impl; 002 003 import org.apache.commons.logging.Log; 004 import org.apache.hivemind.util.Defense; 005 import org.apache.tapestry.IRequestCycle; 006 import org.apache.tapestry.engine.IEngineService; 007 import org.apache.tapestry.engine.ILink; 008 import org.apache.tapestry.error.RequestExceptionReporter; 009 import org.apache.tapestry.services.LinkFactory; 010 import org.apache.tapestry.services.ServiceConstants; 011 import org.apache.tapestry.util.ContentType; 012 import org.apache.tapestry.web.WebRequest; 013 import org.apache.tapestry.web.WebResponse; 014 015 import javax.imageio.ImageIO; 016 import javax.servlet.http.HttpServletResponse; 017 import java.awt.image.BufferedImage; 018 import java.io.ByteArrayOutputStream; 019 import java.io.IOException; 020 import java.io.OutputStream; 021 import java.util.HashMap; 022 import java.util.Map; 023 024 /** 025 * Provides generated rounded corner images in a similar use / fashion as 026 * outlined here: <a href="http://xach.livejournal.com/95656.html">google's own cornershop</a>. 027 */ 028 public class RoundedCornerService implements IEngineService { 029 030 public static final String SERVICE_NAME = "rounded"; 031 032 public static final String PARM_COLOR = "c"; 033 public static final String PARM_BACKGROUND_COLOR = "bc"; 034 public static final String PARM_WIDTH = "w"; 035 public static final String PARM_HEIGHT = "h"; 036 public static final String PARM_ANGLE = "a"; 037 038 public static final String PARM_SHADOW_WIDTH ="sw"; 039 public static final String PARM_SHADOW_OPACITY ="o"; 040 public static final String PARM_SHADOW_SIDE = "s"; 041 042 public static final String PARM_WHOLE_SHADOW = "shadow"; 043 public static final String PARM_ARC_HEIGHT = "ah"; 044 public static final String PARM_ARC_WIDTH = "aw"; 045 046 private static final long MONTH_SECONDS = 60 * 60 * 24 * 30; 047 048 private static final long EXPIRES = System.currentTimeMillis() + 365 * 24 * 60 * 60 * 1000L; 049 050 private RequestExceptionReporter _exceptionReporter; 051 052 private LinkFactory _linkFactory; 053 054 private WebRequest _request; 055 056 private WebResponse _response; 057 058 private RoundedCornerGenerator _generator = new RoundedCornerGenerator(); 059 060 // holds pre-built binaries for previously generated colors 061 private Map _imageCache = new HashMap(); 062 063 private Log _log; 064 065 /** The ImageIO format name to encode images in that don't need alpha transparency */ 066 private String _nonTransparentFormatName = "gif"; 067 068 public void initialize() 069 { 070 String[] names = ImageIO.getWriterFormatNames(); 071 072 boolean supportsGif = false; 073 074 for (int i=0; i < names.length; i++) 075 { 076 if (names[i].toLowerCase().equals("gif")) 077 { 078 supportsGif = true; 079 break; 080 } 081 } 082 083 if (!supportsGif) 084 { 085 _nonTransparentFormatName = "jpeg"; 086 } 087 } 088 089 public ILink getLink(boolean post, Object parameter) 090 { 091 Defense.notNull(parameter, "parameter"); 092 Defense.isAssignable(parameter, Object[].class, "parameter"); 093 094 Object[] parms = (Object[]) parameter; 095 096 Map parameters = new HashMap(); 097 parameters.put(ServiceConstants.SERVICE, getName()); 098 parameters.put(ServiceConstants.PARAMETER, parms); 099 100 return _linkFactory.constructLink(this, post, parameters, false); 101 } 102 103 public void service(IRequestCycle cycle) 104 throws IOException 105 { 106 if (_request.getHeader("If-Modified-Since") != null) 107 { 108 _response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); 109 return; 110 } 111 112 String color = cycle.getParameter(PARM_COLOR); 113 String bgColor = cycle.getParameter(PARM_BACKGROUND_COLOR); 114 int width = getIntParam(cycle.getParameter(PARM_WIDTH)); 115 int height = getIntParam(cycle.getParameter(PARM_HEIGHT)); 116 String angle = cycle.getParameter(PARM_ANGLE); 117 118 int shadowWidth = getIntParam(cycle.getParameter(PARM_SHADOW_WIDTH)); 119 float shadowOpacity = getFloatParam(cycle.getParameter(PARM_SHADOW_OPACITY)); 120 String side = cycle.getParameter(PARM_SHADOW_SIDE); 121 122 boolean wholeShadow = Boolean.valueOf(cycle.getParameter(PARM_WHOLE_SHADOW)).booleanValue(); 123 float arcWidth = getFloatParam(cycle.getParameter(PARM_ARC_WIDTH)); 124 float arcHeight = getFloatParam(cycle.getParameter(PARM_ARC_HEIGHT)); 125 126 String hashKey = color + bgColor + width + height + angle + shadowWidth + shadowOpacity + side + wholeShadow; 127 128 ByteArrayOutputStream bo = null; 129 130 try { 131 132 String type = (bgColor != null) ? _nonTransparentFormatName : "png"; 133 134 byte[] data = (byte[])_imageCache.get(hashKey); 135 if (data != null) 136 { 137 writeImageResponse(data, type); 138 return; 139 } 140 141 BufferedImage image = null; 142 143 if (wholeShadow) 144 { 145 image = _generator.buildShadow(color, bgColor, width, height, arcWidth, arcHeight, shadowWidth, shadowOpacity); 146 } else if (side != null) 147 { 148 image = _generator.buildSideShadow(side, shadowWidth, shadowOpacity); 149 } else 150 { 151 image = _generator.buildCorner(color, bgColor, width, height, angle, shadowWidth, shadowOpacity); 152 } 153 154 bo = new ByteArrayOutputStream(); 155 156 boolean success = ImageIO.write(image, type, bo); 157 158 data = bo.toByteArray(); 159 160 if (!success || data == null || data.length < 1) 161 { 162 _log.error("Image generated had zero length byte array or failed to convert from parameters of:\n" 163 + "[color:" + color + ", bgColor:" + bgColor 164 + ", width:" + width + ", height:" + height 165 + ", angle:" + angle + ", shadowWidth:" + shadowWidth 166 + ", shadowOpacity:" + shadowOpacity + ", side:" + side 167 + ", wholeShadow: " + wholeShadow + ", arcWidth: " + arcWidth 168 + ", arcHeight:" + arcHeight + "\n image: " + image); 169 170 _response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); 171 return; 172 } 173 174 _imageCache.put(hashKey, data); 175 176 writeImageResponse(data, type); 177 178 } catch (IOException eof) 179 { 180 // ignored / expected exceptions happen when browser prematurely abandons connections - IE does this a lot 181 } catch (Throwable ex) { 182 183 ex.printStackTrace(); 184 _exceptionReporter.reportRequestException("Error creating image.", ex); 185 } finally { 186 try { 187 if (bo != null) { 188 bo.close(); 189 } 190 } catch (Throwable t) { 191 // ignore 192 } 193 194 } 195 } 196 197 void writeImageResponse(byte[] data, String type) 198 throws Exception 199 { 200 OutputStream os = null; 201 202 try { 203 _response.setDateHeader("Expires", EXPIRES); 204 _response.setHeader("Cache-Control", "public, max-age=" + (MONTH_SECONDS * 3)); 205 _response.setContentLength(data.length); 206 207 os = _response.getOutputStream(new ContentType("image/" + type)); 208 209 os.write(data); 210 211 } finally { 212 try { 213 if (os != null) { 214 os.flush(); 215 os.close(); 216 } 217 } catch (Throwable t) { 218 // ignore 219 } 220 } 221 } 222 223 private int getIntParam(String value) 224 { 225 if (value == null) 226 return -1; 227 228 return Integer.valueOf(value).intValue(); 229 } 230 231 private float getFloatParam(String value) 232 { 233 if (value == null) 234 return -1f; 235 236 return Float.valueOf(value).floatValue(); 237 } 238 239 public String getName() 240 { 241 return SERVICE_NAME; 242 } 243 244 /* Injected */ 245 public void setExceptionReporter(RequestExceptionReporter exceptionReporter) 246 { 247 _exceptionReporter = exceptionReporter; 248 } 249 250 /* Injected */ 251 public void setLinkFactory(LinkFactory linkFactory) 252 { 253 _linkFactory = linkFactory; 254 } 255 256 /* Injected */ 257 public void setRequest(WebRequest request) 258 { 259 _request = request; 260 } 261 262 /* Injected */ 263 public void setResponse(WebResponse response) 264 { 265 _response = response; 266 } 267 268 public void setLog(Log log) 269 { 270 _log = log; 271 } 272 }