source: main/trunk/greenstone3/src/java/org/greenstone/gsdl3/OAIServer.java@ 28985

Last change on this file since 28985 was 28985, checked in by kjdon, 10 years ago

use OAIMessageRouter instead of MessageRouter. this will use OAICollection. then only the oai services will be loaded, not other colleciton services or site wide services, none of which are needed for oai server

File size: 17.0 KB
Line 
1/*
2 * OAIServer.java
3 * Copyright (C) 2002 New Zealand Digital Library, http://www.nzdl.org
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19package org.greenstone.gsdl3;
20
21import java.io.IOException;
22import java.io.PrintWriter;
23import java.util.HashSet;
24import java.util.Iterator;
25import java.util.Map;
26
27import javax.servlet.ServletConfig;
28import javax.servlet.ServletException;
29import javax.servlet.UnavailableException;
30import javax.servlet.http.HttpServletRequest;
31import javax.servlet.http.HttpServletResponse;
32
33import org.apache.log4j.Logger;
34import org.greenstone.gsdl3.comms.Communicator;
35import org.greenstone.gsdl3.comms.SOAPCommunicator;
36import org.greenstone.gsdl3.core.OAIMessageRouter;
37import org.greenstone.gsdl3.core.OAIReceptionist;
38import org.greenstone.gsdl3.util.GSConstants;
39import org.greenstone.gsdl3.util.GSParams;
40import org.greenstone.gsdl3.util.GSXML;
41import org.greenstone.gsdl3.util.OAIResumptionToken;
42import org.greenstone.gsdl3.util.OAIXML;
43import org.greenstone.gsdl3.util.XMLConverter;
44import org.w3c.dom.Document;
45import org.w3c.dom.Element;
46import org.w3c.dom.Node;
47
48/** a servlet to serve the OAI metadata harvesting - we are using servlets instead
49 * of cgi
50 * the init method is called only once - the first time the servlet classes
51 * are loaded. Each time a request comes in to the servlet, the session()
52 * method is called in a new thread (calls doGet/doPut etc)
53 * takes the verb= type args and builds a simple request to send to
54 * the oai receptionist, which returns a result in xml, conforming to the OAI-PMH
55 * protocol.
56 * @see Receptionist
57 */
58/**
59 * OAI server configuration instructions *
60 *
61 */
62public class OAIServer extends BaseGreenstoneServlet
63{
64
65 /** the receptionist to send messages to */
66 protected OAIReceptionist recept = null;
67 /**
68 * the default language - is specified by setting a servlet param, otherwise
69 * DEFAULT_LANG is used
70 */
71 protected String default_lang = null;
72 /**
73 * The default default - used if a default lang is not specified in the
74 * servlet params
75 */
76 protected final String DEFAULT_LANG = "en";
77
78 /** A HashSet which contains all the legal verbs. */
79 protected HashSet<String> verb_set = null;
80 /**
81 * A HashSet which contains all the legal oai keys in the key/value argument
82 * pair.
83 */
84 protected HashSet<String> param_set = null;
85 /**
86 * The name of the site with which we will finally be dealing, whether it is
87 * a local site or a remote site through a communicator.
88 */
89 protected String site = "";
90
91 // can be overriddden in OAIConfig.xml
92 // do we output the stylesheet processing instruction?
93 protected boolean use_oai_stylesheet = true;
94 protected String oai_stylesheet = "interfaces/oai/oai2.xsl";
95
96 // there is no getQueryString() method in the HttpServletRequest returned from doPost,
97 // since that is actually of type apache RequestFacade, and doesn't define such a method
98 protected String queryString = null;
99
100 static Logger logger = Logger.getLogger(org.greenstone.gsdl3.OAIServer.class.getName());
101
102 /**
103 * initialise the servlet
104 */
105 public void init(ServletConfig config) throws ServletException
106 {
107 // always call super.init, i.e., HttpServlet.;
108 super.init(config);
109 this.default_lang = config.getInitParameter(GSConstants.DEFAULT_LANG);
110
111 initVerbs();
112 initParams();
113
114 String site_name = config.getInitParameter(GSConstants.SITE_NAME);
115 String remote_site_name = null;
116 String remote_site_type = null;
117 String remote_site_address = null;
118
119 if (site_name == null)
120 {
121 // no local site, try for communicator (remote site)
122 remote_site_name = config.getInitParameter("remote_site_name");
123 remote_site_type = config.getInitParameter("remote_site_type");
124 remote_site_address = config.getInitParameter("remote_site_address");
125 if (remote_site_name == null || remote_site_type == null || remote_site_address == null)
126 {
127 logger.error("initialisation paramters not all set!");
128 logger.error("if site_name is not set, then you must have remote_site_name, remote_site_type and remote_site_address set");
129 throw new UnavailableException("OAIServer: incorrect servlet parameters");
130 }
131 }
132
133 if (this.default_lang == null)
134 {
135 // choose english
136 this.default_lang = DEFAULT_LANG;
137 }
138
139 // the receptionist -the servlet will talk to this
140 this.recept = new OAIReceptionist();
141
142 // the receptionist uses a OAIMessageRouter or Communicator to send its requests to. We either create a OAIMessageRouter 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.
143 if (site_name != null)
144 {
145 //this site_name could consist of comma separated more than one site name.
146 String mr_name = (String) config.getInitParameter("messagerouter_class");
147 OAIMessageRouter message_router = null;
148 if (mr_name == null)
149 { // just use the normal MR *********
150 message_router = new OAIMessageRouter();
151 }
152 else
153 { // try the specified one
154 try
155 {
156 message_router = (OAIMessageRouter) Class.forName("org.greenstone.gsdl3.core." + mr_name).newInstance();
157 }
158 catch (Exception e)
159 { // cant use this new one, so use normal one
160 logger.error("OAIServlet configure exception when trying to use a new OAIMessageRouter " + mr_name, e);
161 message_router = new OAIMessageRouter();
162 }
163 }
164
165 message_router.setSiteName(site_name);
166 // lots of work is done in this step; see OAIMessageRouter.java
167 if (!message_router.configure()) {
168 throw new UnavailableException("OAIServer: Couldn't configure OAIMessageRouter");
169 }
170 this.recept.setSiteName(site_name);
171 this.recept.setMessageRouter(message_router);
172
173 }
174 else
175 {
176 // talking to a remote site, create a communicator
177 Communicator communicator = null;
178 // we need to create the XML to configure the communicator
179 Document site_doc = XMLConverter.newDOM();
180 Element site_elem = site_doc.createElement(GSXML.SITE_ELEM);
181 site_elem.setAttribute(GSXML.TYPE_ATT, remote_site_type);
182 site_elem.setAttribute(GSXML.NAME_ATT, remote_site_name);
183 site_elem.setAttribute(GSXML.ADDRESS_ATT, remote_site_address);
184
185 if (remote_site_type.equals(GSXML.COMM_TYPE_SOAP_JAVA))
186 {
187 communicator = new SOAPCommunicator();
188 }
189 else
190 {
191 logger.error("OAIServlet.init Error: invalid Communicator type: " + remote_site_type);
192 throw new UnavailableException("OAIServer: invalid communicator type");
193 }
194
195 if (!communicator.configure(site_elem))
196 {
197 logger.error("OAIServlet.init Error: Couldn't configure communicator");
198 throw new UnavailableException("OAIServer: Couldn't configure communicator");
199 }
200 this.recept.setSiteName(remote_site_name);
201 this.recept.setMessageRouter(communicator);
202 }
203
204 // Read in OAIConfig.xml (residing web/WEB-INF/classes/) and
205 //use it to configure the receptionist.
206 Element oai_config = OAIXML.getOAIConfigXML();
207 if (oai_config == null)
208 {
209 logger.error("Fail to parse oai config file OAIConfig.xml.");
210 throw new UnavailableException("OAIServer: Couldn't parse OAIConfig.xml");
211 }
212 // pass it to the receptionist
213 if (!this.recept.configure(oai_config)) {
214 logger.error("Couldn't configure receptionist");
215 throw new UnavailableException("OAIServer: Couldn't configure receptionist");
216 }
217 // also, we have something we want to get from here - useOAIStylesheet
218 this.configure(oai_config);
219 // Initialise the resumption tokens
220 OAIResumptionToken.init();
221
222 }//end of init()
223
224 private void configure(Element oai_config)
225 {
226 Element use_stylesheet_elem = (Element) GSXML.getChildByTagName(oai_config, OAIXML.USE_STYLESHEET);
227 if (use_stylesheet_elem != null)
228 {
229 String value = GSXML.getNodeText(use_stylesheet_elem);
230 if (value.equals("no"))
231 {
232 this.use_oai_stylesheet = false;
233 }
234 }
235 if (this.use_oai_stylesheet)
236 {
237 // now see if there is a custom stylesheet specified
238 Element stylesheet_elem = (Element) GSXML.getChildByTagName(oai_config, OAIXML.STYLESHEET);
239 if (stylesheet_elem != null)
240 {
241 String value = GSXML.getNodeText(stylesheet_elem);
242 if (!value.equals(""))
243 {
244 oai_stylesheet = value;
245 }
246 }
247
248 }
249 }
250
251 private void initVerbs()
252 {
253 verb_set = new HashSet<String>();
254 verb_set.add(OAIXML.GET_RECORD);
255 verb_set.add(OAIXML.LIST_RECORDS);
256 verb_set.add(OAIXML.LIST_IDENTIFIERS);
257 verb_set.add(OAIXML.LIST_SETS);
258 verb_set.add(OAIXML.LIST_METADATA_FORMATS);
259 verb_set.add(OAIXML.IDENTIFY);
260 }
261
262 private void initParams()
263 {
264 param_set = new HashSet<String>();
265 param_set.add(OAIXML.METADATA_PREFIX);
266 param_set.add(OAIXML.FROM);
267 param_set.add(OAIXML.UNTIL);
268 param_set.add(OAIXML.SET);
269 param_set.add(OAIXML.RESUMPTION_TOKEN);
270 param_set.add(OAIXML.IDENTIFIER);
271 }
272
273 private void logUsageInfo(HttpServletRequest request)
274 {
275 String usageInfo = "";
276
277 String query = (queryString == null) ? request.getQueryString() : queryString;
278
279 //logged info = general-info + session-info
280 usageInfo = request.getContextPath() + " " + //session id
281 request.getServletPath() + " " + //serlvet
282 "[" + query + "]" + " " + //the query string
283 "[" + usageInfo.trim() + "]" + " " + // params stored in a session
284 request.getRemoteAddr() + " " + //remote address
285 request.getHeader("user-agent") + " "; //the remote brower info
286
287 logger.info(usageInfo);
288 }
289
290 /**
291 * return true if the url is in the form of baseURL?verb=...,
292 */
293 private boolean validate(String query, String verb)
294 {
295 //Here in OAIServer, only the verbs are validated. All the validation for individual verb
296 // is taken in their doXXX() methods.
297 if (query == null || !query.startsWith(OAIXML.VERB + "="))
298 {
299 return false;
300 }
301 if (!verb_set.contains(verb))
302 {
303 return false;
304 }
305 return true;
306 }
307
308 private String getVerb(String query)
309 {
310 if (query == null)
311 return "";
312 int verb_start_index = query.indexOf("=") + 1;// first occurence of '='
313 int verb_end_index = query.indexOf("&");
314 if (verb_end_index == -1)
315 {
316 return query.substring(verb_start_index);
317 }
318 return query.substring(verb_start_index, verb_end_index);
319 }
320
321 public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
322 {
323 logUsageInfo(request);
324
325 // oai always requires the content type be text/xml
326 request.setCharacterEncoding("UTF-8");
327 response.setContentType("text/xml;charset=UTF-8");
328 PrintWriter out = response.getWriter();
329
330 //
331 String lang = request.getParameter(GSParams.LANGUAGE);
332 if (lang == null || lang.equals(""))
333 {
334 // use the default
335 lang = this.default_lang;
336 }
337 //we don't get the baseURL from the http request because what we get might be different from the one known publicly due to local network redirection.
338 //For example, puka.cs.waikato.ac.nz vs www.greenstone.org
339 //String base_url = request.getRequestURL().toString();
340
341 // if called by doPost (if this was originally a POST request), var queryString would have been set
342 String query = (queryString == null) ? request.getQueryString() : queryString;
343 queryString = null; // reset member variable, else no doGet will work as long as the server remains running
344
345 if (query!=null && query.equals("reset")) {
346 logger.error("reset was called*******************");
347 out.println("<?xml version='1.0' encoding='UTF-8' ?>");
348 out.println(this.recept.process("<message><request reset='true'/></message>"));
349 return;
350 }
351 String[] pairs = (query == null) ? null : query.split("&");//split into key/value pairs
352
353 String verb = getVerb(query);
354 Document response_doc = XMLConverter.newDOM();
355 Element xml_response = OAIXML.createBasicResponse(response_doc, verb, pairs);
356 Element verb_elem = null;
357
358 if (validate(query, verb) == false)
359 {
360 if (verb_set.contains(verb) == false)
361 {
362 logger.error(OAIXML.BAD_VERB + ": " + query);
363 verb_elem = OAIXML.createErrorElement(response_doc, OAIXML.BAD_VERB, OAIXML.ILLEGAL_OAI_VERB);
364 }
365 else
366 {
367 //must be something else other than bad verbs caused an error, so bad argument
368 logger.error(OAIXML.BAD_ARGUMENT + ": " + query);
369 verb_elem = OAIXML.createErrorElement(response_doc, OAIXML.BAD_ARGUMENT, "");
370 }
371 xml_response.appendChild(verb_elem);
372
373 out.println("<?xml version='1.0' encoding='UTF-8' ?>");
374 if (this.use_oai_stylesheet)
375 {
376 out.println("<?xml-stylesheet type='text/xsl' href='" + this.oai_stylesheet + "' ?>\n");
377 }
378 out.println(XMLConverter.getPrettyString(xml_response));
379 return;
380 }//end of if(validate
381
382 // The query is valid, we can now
383 // compose the request message to the receptionist
384 Document request_doc = XMLConverter.newDOM();
385 Element xml_message = request_doc.createElement(GSXML.MESSAGE_ELEM);
386 Element xml_request = request_doc.createElement(GSXML.REQUEST_ELEM);
387 // The type attribute is set to be 'oaiService' from OAIServer to OAIReceptionist.
388 //xml_request.setAttribute(GSXML.TYPE_ATT, OAIXML.OAI_SERVICE);
389 xml_request.setAttribute(GSXML.LANG_ATT, lang);
390 xml_request.setAttribute(GSXML.TO_ATT, verb);
391 addParams(xml_request, pairs);
392
393 //xml_request.setAttribute(GSXML.OUTPUT_ATT, output);????
394 xml_message.appendChild(xml_request);
395
396 Node xml_result = this.recept.process(xml_message);
397 if (xml_result == null)
398 {
399 logger.info("xml_result is null");
400 verb_elem = OAIXML.createErrorElement(response_doc, "Internal error", "");
401 xml_response.appendChild(verb_elem);
402 }
403 else
404 {
405
406 /**
407 * All response elements are in the form (with a corresponding verb
408 * name): <message> <response> <verb> ... <resumptionToken> .. this
409 * is optional! </resumptionToken> </verb> </response> </message>
410 */
411 Node res = GSXML.getChildByTagName(xml_result, GSXML.RESPONSE_ELEM);
412 if (res == null)
413 {
414 logger.info("response element in xml_result is null");
415 verb_elem = OAIXML.createErrorElement(response_doc, "Internal error", "");
416 }
417 else
418 {
419 verb_elem = GSXML.getFirstElementChild(res);
420 }
421
422 if ( verb_elem.getTagName().equals(OAIXML.ERROR))
423 {
424 xml_response.appendChild(response_doc.importNode(verb_elem, true));
425 }
426 else if (OAIXML.oai_version.equals(OAIXML.OAI_VERSION2)) {
427 xml_response.appendChild(response_doc.importNode(verb_elem, true));
428 }
429 else
430 {
431 GSXML.copyAllChildren(xml_response, verb_elem);
432 }
433 }
434 out.println("<?xml version='1.0' encoding='UTF-8' ?>");
435 if (this.use_oai_stylesheet)
436 {
437 out.println("<?xml-stylesheet type='text/xsl' href='" + this.oai_stylesheet + "' ?>\n");
438 }
439 out.println(XMLConverter.getPrettyString(xml_response));
440 return;
441 }
442
443 /** append parameter elements to the request sent to the receptionist */
444 public void addParams(Element request, String[] pairs)
445 {
446 Document doc = request.getOwnerDocument();
447 // no params apart from the verb
448 if (pairs == null || pairs.length < 2)
449 return;
450
451 /**
452 * the request xml is composed in the form: <request> <param name=.../>
453 * <param name=.../> </request> (No paramList element in between).
454 */
455 for (int i = 1; i < pairs.length; i++)
456 {
457 //the first pair in pairs is the verb=xxx
458 int index = pairs[i].indexOf("=");
459 if (index != -1)
460 { //just a double check
461 Element param = GSXML.createParameter(doc, pairs[i].substring(0, index), OAIXML.oaiDecode(pairs[i].substring(index + 1)));
462 request.appendChild(param);
463 }
464 }
465 }
466
467 // For OAI version 2.0, validation tests indicated that POST needs to be supported. Some
468 // modification was required in order to ensure that the request is passed intact to doGet()
469 public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
470 {
471
472 // the post method returns a wrapper of type RequestFacade by apache and there
473 // is no getQueryString() method defined for it. Therefore, need to work this out
474 // manually before calling doGet(request, response) so that doGet can work as before.
475
476 queryString = "";
477 Iterator parameter_entries = request.getParameterMap().entrySet().iterator();
478 while (parameter_entries.hasNext())
479 {
480 Map.Entry param_entry = (Map.Entry) parameter_entries.next();
481 String[] paramVals = (String[]) param_entry.getValue();
482 if (paramVals != null)
483 {
484 if (paramVals.length > 0)
485 {
486 logger.error("POST request received: " + param_entry.getKey() + " - " + paramVals[0]);
487 queryString = queryString + "&" + param_entry.getKey() + "=" + paramVals[0];
488 }
489 }
490 }
491 if (queryString.length() > 0)
492 {
493 queryString = queryString.substring(1);
494 //queryString = OAIXML.oaiEncode(queryString);
495 }
496 if (queryString.equals(""))
497 {
498 queryString = null;
499 }
500 doGet(request, response);
501 }
502
503
504 public void destroy()
505 {
506 recept.cleanUp();
507 }
508
509}
Note: See TracBrowser for help on using the repository browser.