source: greenstone3/trunk/src/java/org/greenstone/gsdl3/LibraryServlet.java@ 16938

Last change on this file since 16938 was 16938, checked in by kjdon, 16 years ago

modified session caching stuff so that if there is no collection name, use the service name instead as the key

  • Property svn:keywords set to Author Date Id Revision
File size: 23.0 KB
Line 
1package org.greenstone.gsdl3;
2
3import org.greenstone.gsdl3.comms.*;
4import org.greenstone.gsdl3.core.*;
5import org.greenstone.gsdl3.util.*;
6import org.greenstone.gsdl3.action.PageAction; // used to get the default action
7import org.w3c.dom.Document;
8import org.w3c.dom.Element;
9import org.w3c.dom.Node;
10import org.w3c.dom.NodeList;
11import java.io.*;
12import javax.servlet.*;
13import javax.servlet.http.*;
14import java.util.Enumeration;
15import java.util.ArrayList;
16import java.util.HashMap;
17import java.io.File;
18import java.util.Hashtable;
19import org.apache.log4j.*;
20
21
22/** a servlet to serve the greenstone library - we are using servlets instead
23 * of cgi
24 * the init method is called only once - the first time the servlet classes
25 * are loaded. Each time a request comes in to the servlet, the session()
26 * method is called in a new thread (calls doGet/doPut etc)
27 * takes the a=p&p=home type args and builds a simple request to send to
28 * its receptionist, which returns a result in html, cos output=html
29 * is set in the request
30 *
31 * 18/Jul/07 xiao
32 * modify to make the cached parameters collection-specific.
33 * Most of the work is done in doGet(), except adding an inner class UserSessionCache.
34 *
35 * @see Receptionist
36 */
37public class LibraryServlet extends HttpServlet {
38
39 /** the receptionist to send messages to */
40 protected Receptionist recept=null;
41 /** the default language - is specified by setting a servlet param,
42 * otherwise DEFAULT_LANG is used*/
43 protected String default_lang= null;
44 /** The default default - used if a default lang is not specified
45 * in the servlet params */
46 protected final String DEFAULT_LANG = "en";
47 /** container Document to create XML Nodes */
48 protected Document doc=null;
49 /** a converter class to parse XML and create Docs */
50 protected XMLConverter converter=null;
51 /** the cgi stuff - the Receptionist can add new args to this
52 *
53 * its used by the servlet to determine what args to save */
54 protected GSParams params = null;
55
56 /** user id - new one per session. This doesn't work if session state is saved between restarts - this requires this value to be saved too. */
57 protected int next_user_id = 0;
58
59 /** a hash that contains all the active session IDs mapped to the cached items
60 * It is updated whenever the whole site or a particular collection is reconfigured
61 * using the command a=s&sa=c or a=s&sa=c&c=xxx
62 * It is in the form: sid -> (UserSessionCache object)
63 */
64 protected Hashtable session_ids_table = new Hashtable();
65
66 /** the maximum interval that the cached info remains in session_ids_table (in seconds)
67 * This is set in web.xml
68 */
69 protected int session_expiration = 1800;
70
71 static Logger logger = Logger.getLogger(org.greenstone.gsdl3.LibraryServlet.class.getName());
72
73 /** initialise the servlet
74 */
75 public void init(ServletConfig config) throws ServletException {
76 // always call super.init;
77 super.init(config);
78 // disable preferences - does this work anyway??
79 //System.setProperty("java.util.prefs.PreferencesFactory", "org.greenstone.gsdl3.util.DisabledPreferencesFactory");
80
81
82 String library_name = config.getInitParameter(GSConstants.LIBRARY_NAME);
83 String gsdl3_home = config.getInitParameter(GSConstants.GSDL3_HOME);
84 String interface_name = config.getInitParameter(GSConstants.INTERFACE_NAME);
85 this.default_lang = config.getInitParameter(GSConstants.DEFAULT_LANG);
86 String sess_expire = config.getInitParameter(GSXML.SESSION_EXPIRATION);
87 if (sess_expire != null && !sess_expire.equals("")) {
88 this.session_expiration = Integer.parseInt(sess_expire);
89 }
90
91 if (library_name == null || interface_name ==null) {
92 // must have this
93 System.err.println("initialisation parameters not all set!");
94 System.err.println(" you must have libraryname and interfacename");
95 System.exit(1);
96 }
97
98 String site_name = config.getInitParameter(GSConstants.SITE_NAME);
99 String remote_site_name = null;
100 String remote_site_type = null;
101 String remote_site_address = null;
102
103 if (site_name == null) {
104 // no site, try for communicator
105 remote_site_name = config.getInitParameter("remote_site_name");
106 remote_site_type = config.getInitParameter("remote_site_type");
107 remote_site_address = config.getInitParameter("remote_site_address");
108 if (remote_site_name == null || remote_site_type == null || remote_site_address == null) {
109 System.err.println("initialisation paramters not all set!");
110 System.err.println("if site_name is not set, then you must have remote_site_name, remote_site_type and remote_site_address set");
111 System.exit(1);
112 }
113 }
114
115 if (this.default_lang == null) {
116 // choose english
117 this.default_lang = DEFAULT_LANG;
118 }
119
120 HashMap config_params = new HashMap();
121
122 config_params.put(GSConstants.LIBRARY_NAME, library_name);
123 config_params.put(GSConstants.INTERFACE_NAME, interface_name);
124 if (site_name != null) {
125 config_params.put(GSConstants.SITE_NAME, site_name);
126 }
127 this.converter = new XMLConverter();
128 this.doc = this.converter.newDOM();
129
130 // the receptionist -the servlet will talk to this
131 String recept_name = (String)config.getInitParameter("receptionist_class");
132 if (recept_name == null) {
133 this.recept = new DefaultReceptionist();
134 } else {
135 try {
136 this.recept = (Receptionist)Class.forName("org.greenstone.gsdl3.core."+recept_name).newInstance();
137 } catch (Exception e) { // cant use this new one, so use normal one
138 System.err.println("LibraryServlet configure exception when trying to use a new Receptionist "+recept_name+": "+e.getMessage());
139 e.printStackTrace();
140 this.recept = new DefaultReceptionist();
141 }
142 }
143 this.recept.setConfigParams(config_params);
144
145 // the receptionist uses a MessageRouter or Communicator to send its requests to. We either create a MessageRouter here for the designated site (if site_name set), or we create a Communicator for a remote site. The is given to teh Receptionist, and the servlet never talks to it again.directly.
146 if (site_name != null) {
147 String mr_name = (String)config.getInitParameter("messagerouter_class");
148 MessageRouter message_router = null;
149 if (mr_name == null) { // just use the normal MR
150 message_router = new MessageRouter();
151 } else { // try the specified one
152 try {
153 message_router = (MessageRouter)Class.forName("org.greenstone.gsdl3.core."+mr_name).newInstance();
154 } catch (Exception e) { // cant use this new one, so use normal one
155 System.err.println("LibraryServlet configure exception when trying to use a new MessageRouter "+mr_name+": "+e.getMessage());
156 e.printStackTrace();
157 message_router = new MessageRouter();
158 }
159 }
160
161 message_router.setSiteName(site_name);
162 message_router.setLibraryName(library_name);
163 message_router.configure();
164 this.recept.setMessageRouter(message_router);
165 } else {
166 // talking to a remote site, create a communicator
167 Communicator communicator = null;
168 // we need to create the XML to configure the communicator
169 Element site_elem = this.doc.createElement(GSXML.SITE_ELEM);
170 site_elem.setAttribute(GSXML.TYPE_ATT, remote_site_type);
171 site_elem.setAttribute(GSXML.NAME_ATT, remote_site_name);
172 site_elem.setAttribute(GSXML.ADDRESS_ATT, remote_site_address);
173
174 if (remote_site_type.equals(GSXML.COMM_TYPE_SOAP_JAVA)) {
175 communicator = new SOAPCommunicator();
176 } else {
177 System.err.println("LibraryServlet.init Error: invalid Communicator type: "+remote_site_type);
178 System.exit(1);
179 }
180
181 if (!communicator.configure(site_elem)) {
182 System.err.println("LibraryServlet.init Error: Couldn't configure communicator");
183 System.exit(1);
184 }
185 this.recept.setMessageRouter(communicator);
186 }
187
188 // the params arg thingy
189
190 String params_name = (String)config.getInitParameter("params_class");
191 if (params_name == null) {
192 this.params = new GSParams();
193 } else {
194 try {
195 this.params = (GSParams)Class.forName("org.greenstone.gsdl3.util."+params_name).newInstance();
196 } catch (Exception e) {
197 System.err.println("LibraryServlet configure exception when trying to use a new params thing "+params_name+": "+e.getMessage());
198 e.printStackTrace();
199 this.params = new GSParams();
200 }
201 }
202 // pass it to the receptionist
203 this.recept.setParams(this.params);
204 this.recept.configure();
205
206 }
207
208
209 private void logUsageInfo(HttpServletRequest request){
210 String usageInfo = "";
211
212 //session-info: get params stored in the session
213 HttpSession session = request.getSession(true);
214 Enumeration attributeNames = session.getAttributeNames();
215 while(attributeNames.hasMoreElements()) {
216 String name = (String)attributeNames.nextElement();
217 usageInfo +=name+"="+session.getAttribute(name)+" ";
218 }
219
220 //logged info = general-info + session-info
221 usageInfo =
222 request.getServletPath()+" "+ //serlvet
223 "["+request.getQueryString()+"]" +" "+ //the query string
224 "["+usageInfo.trim()+"]" +" "+ // params stored in a session
225 request.getRemoteAddr()+" "+ //remote address
226 request.getRequestedSessionId()+" "+ //session id
227 request.getHeader("user-agent")+" "; //the remote brower info
228
229 logger.info(usageInfo);
230
231 }
232
233 public class UserSessionCache implements HttpSessionBindingListener {
234
235 String session_id = "";
236
237 /** a hash that maps the session ID to a hashtable that maps the coll_name to its parameters
238 * coll_name -> Hashtable (param_name -> param_value)
239 */
240 protected Hashtable coll_name_params_table = null;
241
242 public UserSessionCache(String id, Hashtable table) {
243 session_id = id;
244 coll_name_params_table = (table == null)? new Hashtable() : table;
245 }
246
247 protected void cleanupCache(String coll_name) {
248 if (coll_name_params_table.containsKey(coll_name)) {
249 coll_name_params_table.remove(coll_name);
250 }
251 }
252 protected Hashtable getParamsTable() {
253 return coll_name_params_table;
254 }
255 public void valueBound(HttpSessionBindingEvent event) {
256 // Do nothing
257 }
258
259 public void valueUnbound(HttpSessionBindingEvent event) {
260 if(session_ids_table.containsKey(session_id)) {
261 session_ids_table.remove(session_id);
262 }
263 }
264 public int tableSize() {
265 return (coll_name_params_table == null)? 0 : coll_name_params_table.size();
266 }
267 }
268
269 public void doGet (HttpServletRequest request,
270 HttpServletResponse response)
271 throws ServletException, IOException {
272 logUsageInfo (request);
273
274 String query_string = request.getQueryString();
275 if (query_string!=null){
276 String[] query_arr = query_string.split("&");
277 boolean redirect = false;
278 String href = null;
279 String rl = null;
280 for (int i=0;i<query_arr.length;i++){
281 if (query_arr[i].startsWith("el")){
282 if (query_arr[i].substring(query_arr[i].indexOf("=")+1,query_arr[i].length()).equals("direct")){
283 redirect = true;
284 }
285 }else if(query_arr[i].startsWith("href")){
286 href = query_arr[i].substring(query_arr[i].indexOf("=")+1,query_arr[i].length());
287 href = href.replaceAll("%2f", "/");
288 href = href.replaceAll("%7e", "~");
289 href = href.replaceAll("%3f", "?");
290 href = href.replaceAll("%3A", "\\:");
291 }else if(query_arr[i].startsWith("rl")){
292 rl = query_arr[i].substring(query_arr[i].indexOf("=")+1,query_arr[i].length());
293 }
294 }
295 //if query_string contains "el=", the web page will be redirected to the external URl, otherwise a greenstone page with an external URL will be displayed
296 //"rl=0" this is an external link
297 //"rl=1" this is an internal link
298 if ((redirect) && (href != null) && (rl.equals("0"))){// This is an external link, the web page is re-directed to the external URL (&el=&rl=0&href="http://...")
299 response.setContentType("text/xml");
300 response.sendRedirect(href);
301 }
302 }
303 // Nested Diagnostic Configurator to identify the client for
304
305 HttpSession session = request.getSession (true);
306 session.setMaxInactiveInterval(session_expiration);
307 String uid = (String)session.getAttribute (GSXML.USER_ID_ATT);
308 if (uid ==null) {
309 uid = ""+getNextUserId ();
310 session.setAttribute (GSXML.USER_ID_ATT, uid);
311 }
312 request.setCharacterEncoding ("UTF-8");
313 response.setContentType ("text/html;charset=UTF-8");
314 PrintWriter out = response.getWriter ();
315
316 String lang = request.getParameter (GSParams.LANGUAGE);
317 if (lang==null || lang.equals ("")) {
318 // try the session cached lang
319 lang = (String)session.getAttribute (GSParams.LANGUAGE);
320 if (lang==null || lang.equals ("")) {
321 // still not set, use the default
322 lang = this.default_lang;
323 }
324 }
325
326 // set the lang in the session
327 session.setAttribute (GSParams.LANGUAGE, lang);
328
329 String output = request.getParameter (GSParams.OUTPUT);
330 if (output==null || output.equals ("")) {
331 output = "html"; // uses html by default
332 }
333
334 // the request to the receptionist
335 Element xml_message = this.doc.createElement (GSXML.MESSAGE_ELEM);
336 Element xml_request = GSXML.createBasicRequest (this.doc, GSXML.REQUEST_TYPE_PAGE, "", lang, uid);
337 xml_request.setAttribute (GSXML.OUTPUT_ATT, output);
338 xml_message.appendChild (xml_request);
339
340 String action = request.getParameter (GSParams.ACTION);
341 String subaction = request.getParameter (GSParams.SUBACTION);
342 String collection = request.getParameter(GSParams.COLLECTION);
343 String service = request.getParameter(GSParams.SERVICE);
344
345 // We clean up the cache session_ids_table if system
346 // commands are issued (and also don't need to do caching for this request)
347 boolean should_cache = true;
348 if(action != null && action.equals(GSParams.SYSTEM)) {
349 should_cache = false;
350
351 // we may want to remove all collection cache info, or just a specific collection
352 boolean clean_all = true;
353 String clean_collection = null;
354 // system commands are to activate/deactivate stuff
355 // collection param is in the sc parameter.
356 // don't like the fact that it is hard coded here
357 String coll = request.getParameter(GSParams.SYSTEM_CLUSTER);
358 if (coll != null && !coll.equals("")) {
359 clean_all = false;
360 clean_collection = coll;
361 } else {
362 // check other system types
363 if (subaction.equals("a") || subaction.equals("d")) {
364 String module_name = request.getParameter("sn");
365 if (module_name != null && !module_name.equals("")) {
366 clean_all = false;
367 clean_collection = module_name;
368 }
369 }
370 }
371 if (clean_all) {
372 session_ids_table = new Hashtable();
373 session.removeAttribute(GSXML.USER_SESSION_CACHE_ATT);
374 } else {
375 // just clean up info for clean_collection
376 ArrayList cache_list = new ArrayList(session_ids_table.values());
377 for (int i=0; i<cache_list.size(); i++) {
378 UserSessionCache cache = (UserSessionCache)cache_list.get(i);
379 cache.cleanupCache(clean_collection);
380 }
381
382 }
383
384 }
385
386 // cache_key is the collection name, or service name
387 String cache_key = collection;
388 if (cache_key == null || cache_key.equals("")) {
389 cache_key = service;
390 }
391
392 // logger.info("should_cache= " + should_cache);
393
394 //clear the collection-specific cache in the session, since we have no way to know whether this session is
395 //about the same collection as the last session or not.
396 Enumeration attributeNames = session.getAttributeNames();
397 while(attributeNames.hasMoreElements()) {
398 String name = (String)attributeNames.nextElement();
399 if (!name.equals (GSXML.USER_SESSION_CACHE_ATT)
400 && !name.equals (GSParams.LANGUAGE)
401 && !name.equals (GSXML.USER_ID_ATT)) {
402 session.removeAttribute(name);
403 }
404 }
405
406 // create a dumy collection name for Authentication
407 if (subaction != null && subaction.equals("authen")){
408 cache_key = "0000000000";
409 }
410
411 UserSessionCache session_cache = null;
412 Hashtable param_table = null;
413 Hashtable table = null;
414 String sid = session.getId();
415 if (should_cache == true && cache_key != null && !cache_key.equals("")) {
416 if (session_ids_table.containsKey(sid)) {
417 session_cache = (UserSessionCache)session_ids_table.get(sid);
418 param_table = session_cache.getParamsTable();
419 logger.info("collections in table: " + tableToString(param_table));
420 if (param_table.containsKey(cache_key)) {
421 //logger.info("existing table: " + collection);
422 table = (Hashtable)param_table.get(cache_key);
423 } else {
424 table = new Hashtable();
425 param_table.put(cache_key, table);
426 //logger.info("new table: " + collection);
427 }
428 } else {
429 param_table = new Hashtable();
430 table = new Hashtable();
431 param_table.put(cache_key, table);
432 session_cache = new UserSessionCache(sid, param_table);
433 session_ids_table.put(sid, session_cache);
434 session.setAttribute(GSXML.USER_SESSION_CACHE_ATT, session_cache);
435 //logger.info("new session id");
436 }
437 }
438
439 if (action==null || action.equals ("")) {
440 // should we do all the following stuff if using default page?
441 // display the home page - the default page
442 xml_request.setAttribute (GSXML.ACTION_ATT, "p");
443 xml_request.setAttribute (GSXML.SUBACTION_ATT, PageAction.HOME_PAGE);
444 }
445 else {
446 xml_request.setAttribute (GSXML.ACTION_ATT, action);
447 if (subaction != null) {
448 xml_request.setAttribute (GSXML.SUBACTION_ATT, subaction);
449 }
450
451 // create the param list for the greenstone request - includes
452 // the params from the current request and any others from the saved session
453 Element xml_param_list = this.doc.createElement (GSXML.PARAM_ELEM+GSXML.LIST_MODIFIER);
454 xml_request.appendChild (xml_param_list);
455
456 Enumeration params = request.getParameterNames ();
457 while(params.hasMoreElements ()) {
458 String name = (String)params.nextElement ();
459 if (!name.equals (GSParams.ACTION)
460 && !name.equals (GSParams.SUBACTION)
461 && !name.equals (GSParams.LANGUAGE)
462 && !name.equals (GSParams.OUTPUT)) {// we have already dealt with these
463 String value="";
464 String [] values = request.getParameterValues (name);
465 value = values[0];
466 if (values.length > 1) {
467 for (int i=1; i< values.length; i++) {
468 value += ","+values[i];
469 }
470 }
471 // either add it to the param list straight away, or save it to the session and add it later
472 if (this.params.shouldSave (name) && table != null) {
473 table.put(name, value);
474 } else {
475 Element param = this.doc.createElement (GSXML.PARAM_ELEM);
476 param.setAttribute (GSXML.NAME_ATT, name);
477 param.setAttribute (GSXML.VALUE_ATT, GSXML.xmlSafe (value));
478 xml_param_list.appendChild (param);
479 }
480 }
481 }
482 //put everything in the table into the session
483 // do we need to do this? why not just put from table into param list
484 if (table != null) {
485 Enumeration keys = table.keys ();
486 while(keys.hasMoreElements ()) {
487 String name = (String)keys.nextElement();
488 session.setAttribute(name, (String)table.get(name));
489 }
490 }
491
492 // put in all the params from the session cache
493 params = session.getAttributeNames ();
494 while(params.hasMoreElements ()) {
495 String name = (String)params.nextElement ();
496
497 if ( !name.equals (GSXML.USER_SESSION_CACHE_ATT)
498 && !name.equals (GSParams.LANGUAGE)
499 && !name.equals (GSXML.USER_ID_ATT)) {
500 // lang and uid are stored but we dont want it in the param list cos its already in the request
501 Element param = this.doc.createElement (GSXML.PARAM_ELEM);
502 param.setAttribute (GSXML.NAME_ATT, name);
503 String value = GSXML.xmlSafe ((String)session.getAttribute (name));
504 // ugly hack to undo : escaping
505 value = value.replaceAll ("%3A", "\\:");
506 param.setAttribute (GSXML.VALUE_ATT,value);
507 xml_param_list.appendChild (param);
508 }
509 }
510 }
511
512 if (!output.equals ("html")) {
513 response.setContentType ("text/xml"); // for now use text
514 }
515
516 //GSXML.printXMLNode(xml_message);
517
518 Node xml_result = this.recept.process(xml_message);
519 encodeURLs (xml_result, response);
520 out.println (this.converter.getPrettyString (xml_result));
521
522 displaySize(session_ids_table);
523}
524
525 //a debugging method
526 private void displaySize(Hashtable table) {
527 if(table == null) {
528 logger.info("cached table is null");
529 return;
530 }
531 if (table.size() == 0) {
532 logger.info("cached table size is zero");
533 return;
534 }
535 int num_cached_coll = 0;
536 ArrayList cache_list = new ArrayList(table.values());
537 for (int i=0; i<cache_list.size(); i++) {
538 num_cached_coll += ((UserSessionCache)cache_list.get(i)).tableSize();
539 }
540 logger.info("Number of sessions : total number of cached collection info = " + table.size() + " : " + num_cached_coll);
541 }
542 /** merely a debugging method! */
543 private String tableToString(Hashtable table) {
544 String str = "";
545 Enumeration keys = table.keys ();
546 while(keys.hasMoreElements ()) {
547 String name = (String)keys.nextElement();
548 str += name + ", ";
549 }
550 return str;
551 }
552
553 /** this goes through each URL and adds in a session id if needed--
554 * its needed if the browser doesn't accept cookies
555 * also escapes things if needed
556 */
557 protected void encodeURLs(Node dataNode, HttpServletResponse response) {
558
559 if (dataNode == null) {
560 return;
561 }
562
563 Element data =null;
564
565 short nodeType = dataNode.getNodeType();
566 if (nodeType == Node.DOCUMENT_NODE) {
567 Document docNode = (Document)dataNode;
568 data = docNode.getDocumentElement() ;
569 }
570 else {
571 data = (Element)dataNode;
572 }
573
574 // get all the <a> elements
575 NodeList hrefs = data.getElementsByTagName("a");
576 for (int i=0; i<hrefs.getLength(); i++) {
577 Element a = (Element)hrefs.item(i);
578 // ugly hack to get rid of : in the args - interferes with session handling
579 String href = a.getAttribute("href");
580 if (!href.equals("")) {
581 if (href.indexOf("?")!=-1) {
582 String[] parts = href.split("\\?", -1);
583 parts[1]=parts[1].replaceAll(":", "%3A");
584 href = parts[0]+"?"+parts[1];
585 }
586 a.setAttribute("href", response.encodeURL(href));
587 }
588 }
589
590 // now find any submit bits - get all the <form> elements
591 NodeList forms = data.getElementsByTagName("form");
592 for (int i=0; i<forms.getLength(); i++) {
593 Element form = (Element)forms.item(i);
594 form.setAttribute("action", response.encodeURL(form.getAttribute("action")));
595 }
596 // are these the only cases where URLs occur??
597 // we should only do this for greenstone urls?
598
599 }
600
601 synchronized protected int getNextUserId() {
602 next_user_id++;
603 return next_user_id;
604 }
605
606 public void doPost(HttpServletRequest request,
607 HttpServletResponse response)
608 throws ServletException, IOException {
609 doGet(request,response);
610
611 }
612}
Note: See TracBrowser for help on using the repository browser.