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.threeD;
031
032 import java.io.IOException;
033 import java.util.Arrays;
034 import java.util.Collections;
035 import java.util.List;
036
037 import javax.servlet.ServletConfig;
038 import javax.servlet.ServletContext;
039 import javax.servlet.ServletException;
040 import javax.servlet.ServletOutputStream;
041 import javax.servlet.http.HttpServlet;
042 import javax.servlet.http.HttpServletRequest;
043 import javax.servlet.http.HttpServletResponse;
044
045 import org.hd.d.pg2k.svrCore.AllExhibitProperties;
046 import org.hd.d.pg2k.svrCore.CoreConsts;
047 import org.hd.d.pg2k.svrCore.ExhibitAttrUtils;
048 import org.hd.d.pg2k.svrCore.Name;
049 import org.hd.d.pg2k.svrCore.Tuple;
050 import org.hd.d.pg2k.svrCore.MIME.ExhibitMIME;
051 import org.hd.d.pg2k.svrCore.stats.StatsLogger;
052 import org.hd.d.pg2k.webSvr.exhibit.DataSourceBean;
053 import org.hd.d.pg2k.webSvr.util.WebUtils;
054
055
056 /**3D Walkthrough data servlet.
057 * Supports 3D walkthrough app by servicing its simple, small, incremental
058 * data requests.
059 * <p>
060 * All requests are GET requests.
061 * <p>
062 * Also contains static client-side send/receive code
063 * to keep everything in one place.
064 */
065 public final class DataServlet extends HttpServlet
066 {
067 /**Unhook from servlet logger. */
068 @Override
069 public void destroy()
070 {
071 logger.setContext(null); // Stop using servlet logger.
072 super.destroy();
073 }
074
075 /**Hook into servlet logger. */
076 @Override
077 public void init(final ServletConfig servletConfig)
078 throws ServletException
079 {
080 logger.setContext(servletConfig.getServletContext()); // Use servlet.log().
081 super.init(servletConfig);
082 }
083
084 /**Our logger which falls back to System.out if servlet log not available; never null. */
085 private final WebUtils.ServletLoggerWithFallback logger = new WebUtils.ServletLoggerWithFallback();
086
087 /**The stats set to which we log general tunnel servlet stats.
088 * The unique codes are the constants DSVNAME_XXX.
089 */
090 private final StatsLogger.StatsConfig statsIDDSV =
091 new StatsLogger.StatsConfig("3DWTSERVLET",
092 logger, // Use servlet log if poss.
093 false, // Only dump summaries...
094 12 * 3600, // About every 12 hours.
095 true); // Adaptive.
096
097 /**General stats event name: inbound HTTP request. */
098 public static final String DSVNAME_HTTPREQUEST = "HTTP-request";
099
100 /**General stats event name: failures handling input HTTP requests. */
101 public static final String DSVNAME_HTTPFAIL = "HTTP-failure";
102
103 /**General stats event name: inbound HTTP request rejected by request op-code. */
104 public static final String DSVNAME_HTTPREJOP = "HTTP-reqRejectedByOp";
105
106
107
108
109
110 /**Get singleton (per-servlet-context) data pipeline/cache instance.
111 * The config param must not be null, but for some operations
112 * (such as calling destroy()) request can be null.
113 * <p>
114 * This does not cache its return value.
115 */
116 private static DataSourceBean getDataSourceBean(
117 final ServletContext ctxt,
118 final HttpServletRequest request)
119 {
120 // Fetches/creates the data source...
121 final DataSourceBean dataSource =
122 DataSourceBean.getApplicationInstance(ctxt);
123
124 // Ensure that the essential details are set up.
125 dataSource.setServletContext(ctxt);
126 if(request != null)
127 { dataSource.setContextPath(request.getContextPath()); }
128
129 return(dataSource);
130 }
131
132
133 /**Respond to a GET request from 3DWT clients.
134 *
135 * @exception IOException if an input/output error occurs
136 */
137 @Override
138 public void doGet(final HttpServletRequest request,
139 final HttpServletResponse response)
140 throws IOException
141 {
142 // Don't allow this response to be cached.
143 // HTTP/1.0 headers.
144 response.setHeader("Expires", "0");
145 response.setHeader("Pragma", "no-cache");
146 // HTTP/1.1 headers.
147 response.setHeader("Cache-Control", "no-cache");
148
149 //if(IsDebug.isDebug) { System.out.println("[Received 3DWT request...]"); }
150 //if(IsDebug.isDebug) { logger.log("[Received 3DWT request...]"); }
151
152 // Note HTTP the request...
153 StatsLogger.captureDataPoint(statsIDDSV, DSVNAME_HTTPREQUEST);
154
155 final DataSourceBean ds = getDataSourceBean(getServletContext(), request);
156
157 // Get the current set of 3DWT exhibit data...
158 final Tuple.Pair<LightweightMetaDataFetchInterface.GalleryBasicMetaData, List<Name.ExhibitFull>> data = getData(ds);
159
160 // The (printable 7-bit ASCII) result string...
161 final String result;
162
163 // Get operation type.
164 final String opType = request.getParameter(Utils.HP_OPCODE);
165 if(Utils.OP_LUS.equals(opType)) // Most common request, so do first.
166 {
167 // Request for name lookup.
168 try
169 {
170 final List<Name.ExhibitFull> names = data.second;
171 final String ordinals = request.getParameter(Utils.HP_ORD);
172 final String[] nums = ordinals.split(",");
173 // Ignore requested items beyond the maximum allowed per call.
174 final int nResults = Math.min(nums.length, LightweightMetaDataFetchInterface.MAX_NAMES_REQUEST);
175 final Name.ExhibitFull results[] = new Name.ExhibitFull[nResults];
176 int len = nResults-1; // For the separators...
177 for(int i = nResults; --i >= 0; )
178 {
179 final Name.ExhibitFull n = names.get(Integer.parseInt(nums[i], 10));
180 if(n == null) { throw new IOException("bad index"); }
181 results[i] = n;
182 len += n.length();
183 }
184 // Separate the results with commas, like the inputs.
185 // Null items are not allowed.
186 final StringBuilder sb = new StringBuilder(len);
187 for(int i = 0; i < nResults; ++i)
188 {
189 if(i != 0) { sb.append(','); }
190 sb.append(results[i]);
191 }
192 result = sb.toString();
193 }
194 catch(final Exception e)
195 {
196 // Any parameter problem generates a tidy error...
197 response.sendError(HttpServletResponse.SC_BAD_REQUEST);
198 return;
199 }
200 }
201 // else if(Utils.OP_LU.equals(opType)) // Next most common request...
202 // {
203 // // Request for name lookup.
204 // try
205 // {
206 // final List<String> names = data.second;
207 // result = names.get(Integer.parseInt(request.getParameter(Utils.HP_ORD), 10));
208 // }
209 // catch(final Exception e)
210 // {
211 // // Any parameter problem generates a tidy error...
212 // response.sendError(HttpServletResponse.SC_BAD_REQUEST);
213 // return;
214 // }
215 // }
216 else if(Utils.OP_MD.equals(opType))
217 {
218 // Request for metadata.
219 result = data.first.toWireString();
220 }
221 else
222 {
223 // Missing/wrong op-type generates an error.
224 response.sendError(HttpServletResponse.SC_BAD_REQUEST);
225 return;
226 }
227
228 // Write (printable 7-bit ASCII) output in one operation.
229 final int length = result.length();
230 response.setContentLength(length);
231 final byte buf[] = new byte[length];
232 for(int i = length; --i >= 0; ) { buf[i] = (byte) result.charAt(i); }
233 final ServletOutputStream os = response.getOutputStream();
234 os.write(buf);
235 os.flush();
236
237 //if(IsDebug.isDebug) { System.out.println("[Replied to 3DWT request.]"); }
238 //if(IsDebug.isDebug) { logger.log("[Replied to 3DWT request.]"); }
239 }
240
241
242 /**Get the filtered data associated with the current DataSourceBean; never null.
243 * A new value is created on first use and when the AEP changes.
244 * <p>
245 * This call should always be very fast
246 * except when doing the initial filtering/construction of the exhibit list
247 * when there would be a danger of races and redundant resource usage,
248 * so we serialise calls to eliminate this possibility.
249 */
250 private static synchronized Tuple.Pair<LightweightMetaDataFetchInterface.GalleryBasicMetaData, List<Name.ExhibitFull>> getData(final DataSourceBean dsb)
251 throws IOException
252 {
253 assert(dsb != null);
254
255 Tuple.Pair<LightweightMetaDataFetchInterface.GalleryBasicMetaData, List<Name.ExhibitFull>> result;
256 while((result = (Tuple.Pair<LightweightMetaDataFetchInterface.GalleryBasicMetaData, List<Name.ExhibitFull>>) dsb.getAEPLinkedValue(WTKey)) == null)
257 {
258 final AllExhibitProperties aep = dsb.getAllExhibitProperties(-1);
259 final Name.ExhibitFull[] rawNames = aep.select(new AllExhibitProperties.AEPFilter()
260 {
261 /**Only accept results that the 3DWT app can handle.
262 * @return true if the exhibit is suitable
263 */
264 public final boolean accept(final AllExhibitProperties aep,
265 final Name.ExhibitFull exhibitName)
266 {
267 final ExhibitMIME.ExhibitTypeParameters exhibitType = ExhibitMIME.getInputFileType(exhibitName);
268 if(!Utils.supportedTypeFor3DWT(exhibitType)) { return(false); }
269 return(true);
270 }
271 }, null, 0);
272 Arrays.sort(rawNames, ExhibitAttrUtils.getAttrWords().SMART_ORDER);
273 // Create read-only smart-sorted exhibit name List...
274 final List<Name.ExhibitFull> exhibits = Collections.unmodifiableList(Arrays.asList(rawNames));
275 dsb.putIfAbsentAEPLinkedValue(WTKey,
276 new Tuple.Pair<LightweightMetaDataFetchInterface.GalleryBasicMetaData, List<Name.ExhibitFull>>(
277 new LightweightMetaDataFetchInterface.GalleryBasicMetaData(aep.aeid.timestamp ^ exhibits.size(), exhibits.size(), CoreConsts.DEFAULT_TEMPORAL_SLACKNESS_S * 500),
278 exhibits));
279 }
280
281 return(result);
282 }
283
284 /**Key for 3DWT-linked data in DataSourceBean. */
285 private static final DataSourceBean.AEPLinkedKey WTKey = new DataSourceBean.AEPLinkedKey("3DWT");
286
287 /**Unique Serialisation class ID generated by http://random.hd.org/. */
288 private static final long serialVersionUID = -1276945667652232880L;
289 }