001    /*
002    Copyright (c) 1996-2011, Damon Hart-Davis
003    All rights reserved.
004    
005    Redistribution and use in source and binary forms, with or without
006    modification, are permitted provided that the following conditions are
007    met:
008    
009      * Redistributions of source code must retain the above copyright
010        notice, this list of conditions and the following disclaimer.
011    
012      * Redistributions in binary form must reproduce the above copyright
013        notice, this list of conditions and the following disclaimer in the
014        documentation and/or other materials provided with the
015        distribution.
016    
017    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
018    IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
019    TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
020    PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
021    OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
022    SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
023    LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
024    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
025    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
026    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
027    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028    */
029    
030    package org.hd.d.pg2k.webSvr.bgImg;
031    
032    import java.io.IOException;
033    import java.io.OutputStream;
034    import java.util.Random;
035    
036    import javax.servlet.http.HttpServlet;
037    import javax.servlet.http.HttpServletRequest;
038    import javax.servlet.http.HttpServletResponse;
039    
040    import org.hd.d.pg2k.svrCore.Rnd;
041    
042    
043    /**Servlet to serve dynamic and partially-random HTML-page background image.
044     * This serves a randomly-generated image which we would like to be
045     * slightly different for each visitor.
046     * <p>
047     * This is intended to be pleasing on the eye, and possible, if required,
048     * to read normal black text over.  So the image is nearly white with
049     * a slowly changing 'watermark' image.
050     * <p>
051     * To minimise bandwidth fetching this, and CPU time generating it,
052     * this is designed to be served with the "caching" filter in front of it,
053     * so that any one user should typically only fetch a new image once per week
054     * or thereabouts, though each user should get a different one.
055     * Also, the image dimensions and quality are adjusted to those of a
056     * "standard" thumbnail, which does not take you lots of space in memory
057     * or on the wire.
058     * <p>
059     * We may cache the last image generated, so that if the system is busy
060     * the last image may be returned again rather than a new one being
061     * generated.
062     */
063    public final class BgImageServlet extends HttpServlet
064        {
065        /**Respond to a GET request for the content served by this servlet.
066         *
067         * @param request The servlet request we are processing
068         * @param response The servlet response we are producing
069         *
070         * @exception IOException if an input/output error occurs
071         */
072        @Override
073        public void doGet(final HttpServletRequest request,
074                          final HttpServletResponse response)
075            throws IOException // , ServletException
076            {
077            doAction(request, response);
078            }
079    
080        /**Respond to a HEAD request for the content served by this servlet.
081         *
082         * @param request The servlet request we are processing
083         * @param response The servlet response we are producing
084         *
085         * @exception IOException if an input/output error occurs
086         */
087        @Override
088        public void doHead(final HttpServletRequest request,
089                           final HttpServletResponse response)
090            throws IOException // , ServletException
091            {
092            doAction(request, response);
093            }
094    
095        /**Respond to a GET/HEAD request for the content served by this servlet.
096         * Returns the exhibit data, with a correct MIME type.
097         *
098         * @param request The servlet request we are processing
099         * @param response The servlet response we are producing
100         *
101         * @exception IOException if an input/output error occurs
102         */
103        public void doAction(final HttpServletRequest request,
104                             final HttpServletResponse response)
105            throws IOException // , ServletException
106            {
107            // Compute a seed or image selector based on the client IP address.
108            // This is made to select from the full range of bg images
109            // as uniformly as is reasonably possible.
110            final long seed = creationTime ^ request.getRemoteAddr().hashCode();
111    
112            // Repeatable pseudo-random numbers seeded by client IP address.
113            // We assume that it is fast to get a repeatable sequence out of this,
114            // but that the relationship of successive values is non-obvious.
115            final Random pRnd = new Random(seed);
116    
117            // Chose cache slot based on client IP address.
118            final int slot = pRnd.nextInt(_imageCache.length);
119    
120            // Serialise access to the cache content...
121            // And ensure that we don't use more than one thread at once
122            // for generating background images
123            // so as to conserve CPU/memory resources.
124            byte encodedImage[];
125            synchronized(_imageCacheLock)
126                {
127                encodedImage = _imageCache[slot];
128                // Need to compute the image...
129                if(encodedImage == null)
130                    {
131                    // Chose background image type based on client.
132                    final int imgType = pRnd.nextInt(BgImageUtils.allDynGens.size());
133                    final BgImageUtils.DynImageGen dig = BgImageUtils.allDynGens.get(imgType);
134    
135                    // Build image based on client address.
136                    encodedImage = BgImageUtils.generateEncodedImage(
137                        dig.createImage(pRnd.nextInt()));
138                    _imageCache[slot] = encodedImage;
139                    }
140                }
141    
142            // Set the correct MIME type and result length.
143            response.setContentType(BgImageUtils.ETP.mimeType);
144            response.setContentLength(encodedImage.length);
145    
146            // If this is a HEAD, then return without providing the body.
147            if("HEAD".equalsIgnoreCase(request.getMethod()))
148                { return; }
149    
150            try
151                {
152                // Write entire bg image in one go for efficiency.
153                final OutputStream os = response.getOutputStream();
154                os.write(encodedImage);
155                os.flush();
156                }
157            catch(final Throwable t)
158                {
159                getServletContext().log("BgImageServlet caught Throwable", t);
160                }
161            }
162    
163        /**Construction time for this class.
164         * Used to modify the relationship between client IP address
165         * and the background selected.
166         */
167        private static final long creationTime = System.currentTimeMillis();
168    
169        /**Minimum number of different backgrounds seen; strictly positive.
170         * Possibly good if prime to scatter client's more uniformly across
171         * the image set.
172         * <p>
173         * As this value gets larger, memory consumption will go up.
174         * <p>
175         * We set this not to use more than about 1%--2% of total memory,
176         * although we may find an abnormally-small heap size during startup
177         * when this will get computed.
178         */
179        private static final int MIN_BGS = Math.max(11,
180            3 | (int) (Runtime.getRuntime().totalMemory() / (100 * BgImageUtils.dynBgImgMaxBytes)));
181    
182        /**Simple cache by "seed" value of background images as raw byte[]; each entry is computed on demand.
183         * This is indexed by a hash on the client's IP address,
184         * and caches a background image.
185         * <p>
186         * The size of this array determines how many different images we hold,
187         * and more will use more memory.
188         * <p>
189         * Access the array contents only under the _imageCacheLock.
190         * <p>
191         * We vary the size of this array a little on each run to vary the mix.
192         * The array size stays constant while the class remains loaded.
193         */
194        private static final byte[][] _imageCache =
195            new byte[MIN_BGS + Rnd.fastRnd.nextInt(MIN_BGS)][];
196    
197        /**Lock for access to _imageCache. */
198        private static final Object _imageCacheLock = new Object();
199    
200        /**Unique Serialisation class ID generated by http://random&#46;hd&#46;org/. */
201        private static final long serialVersionUID = 2040799493050847367L;
202        }