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 }