View Javadoc

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 		// if no cache, then make one
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 }