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.hd.org/. */
201 private static final long serialVersionUID = 2040799493050847367L;
202 }