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        public ILink getLink(boolean post, Object parameter)
066        {
067            Defense.notNull(parameter, "parameter");
068            Defense.isAssignable(parameter, Object[].class, "parameter");
069            
070            Object[] parms = (Object[]) parameter;
071            
072            Map parameters = new HashMap();
073            parameters.put(ServiceConstants.SERVICE, getName());
074            parameters.put(ServiceConstants.PARAMETER, parms);
075            
076            return _linkFactory.constructLink(this, post, parameters, false);
077        }
078    
079        public void service(IRequestCycle cycle)
080                throws IOException
081        {
082            if (_request.getHeader("If-Modified-Since") != null)
083            {
084                _response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
085                return;
086            }
087    
088            String color = cycle.getParameter(PARM_COLOR);
089            String bgColor = cycle.getParameter(PARM_BACKGROUND_COLOR);
090            int width = getIntParam(cycle.getParameter(PARM_WIDTH));
091            int height = getIntParam(cycle.getParameter(PARM_HEIGHT));
092            String angle = cycle.getParameter(PARM_ANGLE);
093            
094            int shadowWidth = getIntParam(cycle.getParameter(PARM_SHADOW_WIDTH));
095            float shadowOpacity = getFloatParam(cycle.getParameter(PARM_SHADOW_OPACITY));
096            String side = cycle.getParameter(PARM_SHADOW_SIDE);
097    
098            boolean wholeShadow = Boolean.valueOf(cycle.getParameter(PARM_WHOLE_SHADOW)).booleanValue();
099            float arcWidth = getFloatParam(cycle.getParameter(PARM_ARC_WIDTH));
100            float arcHeight = getFloatParam(cycle.getParameter(PARM_ARC_HEIGHT));
101    
102            String hashKey = color + bgColor + width + height + angle + shadowWidth + shadowOpacity + side + wholeShadow;
103    
104            ByteArrayOutputStream bo = null;
105            
106            try {
107                
108                String type = (bgColor != null) ? "gif" : "png";
109    
110                byte[] data = (byte[])_imageCache.get(hashKey);
111                if (data != null)
112                {
113                    writeImageResponse(data, type);
114                    return;
115                }
116    
117                BufferedImage image = null;
118    
119                if (wholeShadow)
120                {
121                    image = _generator.buildShadow(color, bgColor, width, height, arcWidth, arcHeight, shadowWidth, shadowOpacity);
122                } else if (side != null)
123                {
124                    image = _generator.buildSideShadow(side, shadowWidth, shadowOpacity);
125                } else
126                {
127                    image = _generator.buildCorner(color, bgColor, width, height, angle, shadowWidth, shadowOpacity);
128                }
129    
130                bo = new ByteArrayOutputStream();
131    
132                ImageIO.write(image, type, bo);
133    
134                data = bo.toByteArray();
135    
136                if (data == null || data.length < 1)
137                {
138                    _log.error("Image generated had zero length byte array from parameters of:\n"
139                               + "[color:" + color + ", bgColor:" + bgColor
140                               + ", width:" + width + ", height:" + height
141                               + ", angle:" + angle + ", shadowWidth:" + shadowWidth
142                               + ", shadowOpacity:" + shadowOpacity + ", side:" + side
143                               + ", wholeShadow: " + wholeShadow + ", arcWidth: " + arcWidth
144                               + ", arcHeight:" + arcHeight + "\n image: " + image);
145    
146                    _response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
147                    return;
148                }
149    
150                _imageCache.put(hashKey, data);
151    
152                writeImageResponse(data, type);
153                
154            } catch (IOException eof)
155            {
156                // ignored / expected exceptions happen when browser prematurely abandons connections - IE does this a lot
157            } catch (Throwable ex) {
158    
159                ex.printStackTrace();
160                _exceptionReporter.reportRequestException("Error creating image.", ex);
161            } finally {
162                try {
163                    if (bo != null) {
164                        bo.close();
165                    }
166                } catch (Throwable t) {
167                    // ignore
168                }
169    
170            }
171        }
172    
173        void writeImageResponse(byte[] data, String type)
174        throws Exception
175        {
176            OutputStream os = null;
177    
178            try {
179                _response.setDateHeader("Expires", EXPIRES);
180                _response.setHeader("Cache-Control", "public, max-age=" + (MONTH_SECONDS * 3));
181                _response.setContentLength(data.length);
182    
183                os = _response.getOutputStream(new ContentType("image/" + type));
184    
185                os.write(data);
186    
187            }  finally {
188                try {
189                    if (os != null) {
190                        os.flush();
191                        os.close();
192                    }
193                } catch (Throwable t) {
194                    // ignore
195                }
196            }
197        }
198    
199        private int getIntParam(String value)
200        {
201            if (value == null)
202                return -1;
203            
204            return Integer.valueOf(value).intValue();
205        }
206    
207        private float getFloatParam(String value)
208        {
209            if (value == null)
210                return -1f;
211            
212            return Float.valueOf(value).floatValue();
213        }
214    
215        public String getName()
216        {
217            return SERVICE_NAME;
218        }
219    
220        /* Injected */
221        public void setExceptionReporter(RequestExceptionReporter exceptionReporter)
222        {
223            _exceptionReporter = exceptionReporter;
224        }
225    
226        /* Injected */
227        public void setLinkFactory(LinkFactory linkFactory)
228        {
229            _linkFactory = linkFactory;
230        }
231    
232        /* Injected */
233        public void setRequest(WebRequest request)
234        {
235            _request = request;
236        }
237    
238        /* Injected */
239        public void setResponse(WebResponse response)
240        {
241            _response = response;
242        }
243    
244        public void setLog(Log log)
245        {
246            _log = log;
247        }
248    }