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    }