1 package org.restafarian.core.servlets;
2
3 import java.io.IOException;
4 import java.io.PrintWriter;
5 import java.sql.Connection;
6 import java.sql.ResultSet;
7 import java.sql.SQLException;
8 import java.sql.Statement;
9 import java.util.ArrayList;
10 import java.util.Calendar;
11 import java.util.Date;
12 import java.util.GregorianCalendar;
13 import java.util.HashMap;
14 import java.util.Hashtable;
15 import java.util.Iterator;
16 import java.util.List;
17 import java.util.Map;
18 import java.util.TreeMap;
19
20 import javax.naming.Context;
21 import javax.naming.InitialContext;
22 import javax.servlet.ServletException;
23 import javax.servlet.http.HttpServletRequest;
24 import javax.servlet.http.HttpServletResponse;
25 import javax.sql.DataSource;
26
27 /***
28 * <p>This abstract class is the base code for select list servlets.</p>
29 */
30 public abstract class SelectListServletBase extends RestServletBase {
31 private static final long serialVersionUID = 1;
32 private static final String FIELD_MAP_REQ_ATTR_KEY = "select.list.data.field.map";
33 private String contextKey = "";
34 private String dataSourceName = "";
35 private String defaultStartsWith = "";
36 private String defaultContains = "";
37 private String defaultOrderBy = "id";
38 private Map dataFields = new HashMap();
39
40 /***
41 * <p>The Servlet "doGet()" method.</p>
42 *
43 * @param req the <code>HttpServletRequest</code> object
44 * @param res the <code>HttpServletResponse</code> object
45 * @throws ServletException
46 * @throws IOException
47 */
48 public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
49 StringBuffer buffer = new StringBuffer();
50
51 req.setAttribute(FIELD_MAP_REQ_ATTR_KEY, getCurrentDataFields(req));
52 buffer.append("<?xml version=\"1.0\"?>\n");
53 buffer.append("<options size=\"");
54 List options = getOptions(req);
55 if (options != null) {
56 buffer.append(options.size());
57 buffer.append("\">\n");
58 buffer.append(formatOptions(req, options));
59 } else {
60 buffer.append("0\">\n");
61 }
62 buffer.append("</options>\n");
63
64 PrintWriter pw = res.getWriter();
65 pw.println(buffer);
66 }
67
68 /***
69 * <p>The Servlet "doPost()" method.</p>
70 *
71 * @param req the <code>HttpServletRequest</code> object
72 * @param res the <code>HttpServletResponse</code> object
73 * @throws ServletException
74 * @throws IOException
75 */
76 public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
77 res.sendError(405, "Method Not Allowed. Use the \"GET\" method for this URL");
78 }
79
80 /***
81 * <p>The Servlet "doPut()" method.</p>
82 *
83 * @param req the <code>HttpServletRequest</code> object
84 * @param res the <code>HttpServletResponse</code> object
85 * @throws ServletException
86 * @throws IOException
87 */
88 public void doPut(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
89 res.sendError(405, "Method Not Allowed. Use the \"GET\" method for this URL");
90 }
91
92 /***
93 * <p>The Servlet "doDelete()" method.</p>
94 *
95 * @param req the <code>HttpServletRequest</code> object
96 * @param res the <code>HttpServletResponse</code> object
97 * @throws ServletException
98 * @throws IOException
99 */
100 public void doDelete(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
101 res.sendError(405, "Method Not Allowed. Use the \"GET\" method for this URL");
102 }
103
104 /***
105 * <p>This method returns the current map of data fields.</p>
106 *
107 * @param req the <code>HttpServletRequest</code> object
108 * @return the current map of data fields
109 */
110 protected Map getCurrentDataFields(HttpServletRequest req) {
111 return dataFields;
112 }
113
114 /***
115 * <p>This method formats the options.</p>
116 *
117 * @param req the <code>HttpServletRequest</code> object
118 * @param options the <code>List</code> of options
119 * @return the formatted options
120 */
121 protected String formatOptions(HttpServletRequest req, List options) {
122 StringBuffer buffer = new StringBuffer();
123
124 int startIndex = 0;
125 int count = 0;
126 String start = req.getParameter("start");
127 if (start != null && start.length() > 0) {
128 startIndex = Integer.parseInt(start.trim());
129 }
130 String countString = req.getParameter("count");
131 if (countString != null && countString.length() > 0) {
132 count = Integer.parseInt(countString.trim());
133 }
134 if (count < 1) {
135 count = 10;
136 }
137 if (startIndex > 0) {
138 startIndex = startIndex - 1;
139 }
140 int endIndex = startIndex + count;
141 if (endIndex > options.size()) {
142 endIndex = options.size();
143 }
144
145 Map fields = (Map) req.getAttribute(FIELD_MAP_REQ_ATTR_KEY);
146 for (int x=startIndex; x<endIndex; x++) {
147 int index = x + 1;
148 Map thisItem = (Map) options.get(x);
149 buffer.append(" <option index=\"");
150 buffer.append(index);
151 buffer.append("\">\n");
152 Iterator i = fields.keySet().iterator();
153 while (i.hasNext()) {
154 String fieldName = (String) i.next();
155 buffer.append(" <field name=\"");
156 buffer.append(fieldName);
157 buffer.append("\">");
158 if (thisItem.get(fieldName) != null) {
159 buffer.append(thisItem.get(fieldName));
160 }
161 buffer.append("</field>\n");
162 }
163 buffer.append(" </option>\n");
164 }
165
166 return buffer.toString();
167 }
168
169 /***
170 * <p>This method retrieves the requested options from the session.</p>
171 *
172 * @param req the <code>HttpServletRequest</code> object
173 * @return the options
174 */
175 protected List getOptions(HttpServletRequest req) {
176 List options = new ArrayList();
177 Date rightNow = new Date();
178 String startsWith = req.getParameter("startsWith");
179 String contains = req.getParameter("contains");
180 String orderBy = req.getParameter("orderBy");
181 if (startsWith == null || startsWith.length() < 1) {
182 startsWith = defaultStartsWith;
183 }
184 if (contains == null || contains.length() < 1) {
185 contains = defaultContains;
186 }
187 if (orderBy == null || orderBy.length() < 1) {
188 orderBy = defaultOrderBy;
189 }
190 String cacheKey = startsWith + ":" + contains + ":" + orderBy + getCacheKeyQualifier(req);
191 Map queryCache = getQueryCache(req, contextKey);
192 if (queryCache.containsKey(cacheKey)) {
193 Map results = (Map) queryCache.get(cacheKey);
194 options = (List) results.get("options");
195 results.put("lastAccessed", rightNow);
196 Integer accessCount = (Integer) results.get("accessCount");
197 results.put("accessCount", new Integer(accessCount.intValue() + 1));
198 } else {
199 options = fetchOptions(req, startsWith, contains, orderBy);
200 if (options != null && options.size() > 0) {
201 Map results = new HashMap();
202 results.put("creationDate", rightNow);
203 results.put("lastAccessed", rightNow);
204 results.put("accessCount", new Integer(1));
205 results.put("options", options);
206 queryCache.put(cacheKey, results);
207 }
208 }
209
210 return options;
211 }
212
213 /***
214 * <p>This method returns any additional qualifiers for the key to the query cache.</p>
215 *
216 * @param req the <code>HttpServletRequest</code> object
217 * @return the cache key qualifier
218 */
219 protected String getCacheKeyQualifier(HttpServletRequest req) {
220 return "";
221 }
222
223 /***
224 * <p>This method obtains the requested options from the data source.</p>
225 *
226 * @param req the <code>HttpServletRequest</code> object
227 * @param startsWith the "starts with" query parameter
228 * @param contains the "contains" query parameter
229 * @param orderBy the sort order
230 * @return the options
231 */
232 protected List fetchOptions(HttpServletRequest req, String startsWith, String contains, String orderBy) {
233 List options = new ArrayList();
234
235 DataSource dataSource = getDataSource();
236 Connection conn = null;
237 Statement stmt = null;
238 ResultSet rs = null;
239 String qs = getQueryStatement(req, startsWith, contains, orderBy);
240 Map items = new TreeMap();
241 Map fields = (Map) req.getAttribute(FIELD_MAP_REQ_ATTR_KEY);
242 try {
243 conn = dataSource.getConnection();
244 conn.setAutoCommit(false);
245 stmt = conn.createStatement();
246 rs = stmt.executeQuery(qs);
247 while (rs.next()) {
248 Map thisItem = new HashMap();
249 Iterator i = fields.keySet().iterator();
250 while (i.hasNext()) {
251 String fieldName = (String) i.next();
252 String tableField = (String) fields.get(fieldName);
253 thisItem.put(fieldName, filter(rs.getString(tableField)));
254 }
255 if (orderBy == null || orderBy.length() < 1) {
256 items.put(thisItem.get("id"), thisItem);
257 } else {
258 options.add(thisItem);
259 }
260 }
261 } catch (SQLException e) {
262 System.out.println("SQL statement: " + qs);
263 System.out.println("SQL error: " + e.toString() + "; " + e.getMessage());
264 e.printStackTrace();
265 } finally {
266 if (rs != null) {
267 try {
268 rs.close();
269 } catch (SQLException sqle) {
270 System.out.println("SQL error: " + sqle.toString() + "; " + sqle.getMessage());
271 sqle.printStackTrace();
272 }
273 rs = null;
274 }
275 if (stmt != null) {
276 try {
277 stmt.close();
278 } catch (SQLException sqle) {
279 System.out.println("SQL error: " + sqle.toString() + "; " + sqle.getMessage());
280 sqle.printStackTrace();
281 }
282 stmt = null;
283 }
284 if (conn != null) {
285 try {
286 conn.commit();
287 conn.close();
288 } catch (SQLException sqle) {
289 System.out.println("SQL error: " + sqle.toString() + "; " + sqle.getMessage());
290 sqle.printStackTrace();
291 }
292 conn = null;
293 }
294 }
295
296 if (orderBy == null || orderBy.length() < 1) {
297 if (!items.isEmpty()) {
298 Iterator i = items.keySet().iterator();
299 while (i.hasNext()) {
300 String key = i.next().toString();
301 options.add(items.get(key));
302 }
303 }
304 }
305
306 return options;
307 }
308
309 /***
310 * <p>This method creates the SQL statement.</p>
311 *
312 * @param req the <code>HttpServletRequest</code> object
313 * @param startsWith the "starts with" query parameter
314 * @param contains the "contains" query parameter
315 * @param orderBy the sort order
316 * @return the SQL statement
317 */
318 protected String getQueryStatement(HttpServletRequest req, String startsWith, String contains, String orderBy) {
319 return getQueryStatement(startsWith, contains, orderBy);
320 }
321
322 /***
323 * <p>This method creates the SQL statement.</p>
324 *
325 * @param startsWith the "starts with" query parameter
326 * @param contains the "contains" query parameter
327 * @param orderBy the sort order
328 * @return the SQL statement
329 */
330 protected abstract String getQueryStatement(String startsWith, String contains, String orderBy);
331
332 /***
333 * <p>Fetch the query cache from the servlet context, or creates a new one.</p>
334 *
335 * @param req the <code>HttpServletRequest</code> object
336 * @param contextKey the servlet context key
337 */
338 protected static Map getQueryCache(HttpServletRequest req, String contextKey) {
339 Map queryCache = (Map) req.getSession().getServletContext().getAttribute(contextKey);
340
341
342 if (queryCache == null) {
343 queryCache = new HashMap();
344 queryCache.put("_lastCacheReviewDateTime", new Date());
345 req.getSession().getServletContext().setAttribute(contextKey, queryCache);
346 }
347
348 Date lastCacheReviewDate = (Date) queryCache.get("_lastCacheReviewDateTime");
349 if (lastCacheReviewDate.before(getReviewCompareDate())) {
350 reviewQueryCache(queryCache);
351 }
352
353 return queryCache;
354 }
355
356 /***
357 * <p>Review the query cache and remove expired items.</p>
358 */
359 protected static void reviewQueryCache(Map queryCache) {
360 Date cutoffDate = getReviewCompareDate();
361
362 List toBeRemoved = new ArrayList();
363 Iterator i = queryCache.keySet().iterator();
364 while (i.hasNext()) {
365 String key = (String) i.next();
366 if (!"_lastCacheReviewDateTime".equals(key)) {
367 Map results = (Map) queryCache.get(key);
368 Date creationDate = (Date) results.get("creationDate");
369 if (creationDate.before(cutoffDate)) {
370 toBeRemoved.add(key);
371 }
372 }
373 }
374 i = toBeRemoved.iterator();
375 while (i.hasNext()) {
376 queryCache.remove(i.next());
377 }
378
379 queryCache.put("_lastCacheReviewDateTime", new Date());
380 }
381
382 /***
383 * <p>Fetch the query cache from the servlet context, or creates a new one.</p>
384 */
385 protected static Date getReviewCompareDate() {
386 Calendar calendar = new GregorianCalendar();
387 calendar.setTime(new Date());
388 calendar.add(Calendar.DATE, -1);
389 return calendar.getTime();
390 }
391
392 /***
393 * <p>This method is used to look up the <code>DataSource</code>
394 * by name.</p>
395 *
396 * @return <code>dataSource</code> - the <code>DataSource</code>
397 */
398 protected DataSource getDataSource() {
399 DataSource dataSource = null;
400
401 try {
402 Context ctx = new InitialContext(new Hashtable());
403 dataSource = (DataSource) ctx.lookup(dataSourceName);
404 } catch (Throwable t) {
405 t.printStackTrace();
406 }
407
408 return dataSource;
409 }
410
411 /***
412 * @return Returns the contextKey.
413 */
414 public String getContextKey() {
415 return contextKey;
416 }
417 /***
418 * @param contextKey The contextKey to set.
419 */
420 public void setContextKey(String contextKey) {
421 this.contextKey = contextKey;
422 }
423 /***
424 * @return Returns the dataFields.
425 */
426 public Map getDataFields() {
427 return dataFields;
428 }
429 /***
430 * @param dataFields The dataFields to set.
431 */
432 public void setDataFields(Map dataFields) {
433 this.dataFields = dataFields;
434 }
435 /***
436 * @return Returns the dataSourceName.
437 */
438 public String getDataSourceName() {
439 return dataSourceName;
440 }
441 /***
442 * @param dataSourceName The dataSourceName to set.
443 */
444 public void setDataSourceName(String dataSourceName) {
445 this.dataSourceName = dataSourceName;
446 }
447 /***
448 * @return Returns the defaultContains.
449 */
450 public String getDefaultContains() {
451 return defaultContains;
452 }
453 /***
454 * @param defaultContains The defaultContains to set.
455 */
456 public void setDefaultContains(String defaultContains) {
457 this.defaultContains = defaultContains;
458 }
459 /***
460 * @return Returns the defaultOrderBy.
461 */
462 public String getDefaultOrderBy() {
463 return defaultOrderBy;
464 }
465 /***
466 * @param defaultOrderBy The defaultOrderBy to set.
467 */
468 public void setDefaultOrderBy(String defaultOrderBy) {
469 this.defaultOrderBy = defaultOrderBy;
470 }
471 /***
472 * @return Returns the defaultStartsWith.
473 */
474 public String getDefaultStartsWith() {
475 return defaultStartsWith;
476 }
477 /***
478 * @param defaultStartsWith The defaultStartsWith to set.
479 */
480 public void setDefaultStartsWith(String defaultStartsWith) {
481 this.defaultStartsWith = defaultStartsWith;
482 }
483 }