source: main/tags/2.80/gsdl/src/oaiservr/oaiaction.cpp@ 24527

Last change on this file since 24527 was 14282, checked in by xiao, 17 years ago

modify to read the baseURL from oai.cfg using getCollectionConfig() instead of using site_cfg_read() from gsdlsite.cfg.

  • Property svn:keywords set to Author Date Id Revision
File size: 14.6 KB
Line 
1#include "oaiaction.h"
2#include "oaitools.h"
3#include "OIDtools.h"
4#include "recptconfig.h"
5
6#include <string>
7#if defined(GSDL_USE_IOS_H)
8# if defined(__WIN32__)
9# include <strstrea.h> // vc4
10# else
11# include <strstream.h>
12# endif
13#else
14# include <sstream>
15#endif
16
17#include <time.h>
18
19oaiaction::oaiaction(const text_t &name)
20{
21 this->logout = new ofstream("oai.log", ios::app);
22 this->configuration = NULL;
23 this->name = name;
24}
25
26//----------------------------------------------------------------------------------------------
27
28// Over-ridden by child classes
29bool oaiaction::validateAction(recptproto *protocol, oaiargs &params)
30{
31 this->errorType = "";
32 return true;
33}
34
35//----------------------------------------------------------------------------------------------
36
37/**********
38 * Compare the supplied metadataPrefix to all those that
39 * are supported. If there is NO match, return true. If
40 * it DOES match one, return false.
41 */
42bool oaiaction::formatNotSupported(text_t &metaFormat)
43{
44 if (metaFormat == "oai_dc") return false;
45 else if (metaFormat == "rfc1807") return false;
46
47 return true;
48}
49
50//----------------------------------------------------------------------------------------------
51
52/**********
53 * Function for outputting the appropriate error(s) with the (version 2.0) request.
54 * The error(s) MUST be included in the response, and take the form:
55 * <error code="errorType">Description of error</error>
56 */
57void oaiaction::output_error(ostream &output, text_t &errorType)
58{
59 text_t description = "";
60
61 if(errorType == "badArgument"){
62 description = "The request includes illegal arguments, is missing required arguments, repeats arguments or the value for an argument has an illegal syntax";
63 }
64 else if(errorType == "noRecordsMatch"){
65 description = "No record matches all the requested parameters";
66 }
67 else if(errorType == "cannotDisseminateFormat"){
68 description = "The metadata format specified is not supported by the item or by the repository";
69 }
70 else if(errorType == "idDoesNotExist"){
71 description = "The value of the identifier is unknown or illegal in this repository";
72 }
73 else if(errorType == "badVerb"){
74 description = "Value of the verb argument is illegal, missing, or repeated";
75 }
76 else if(errorType == "noMetadataFormats"){
77 description = "There are no metadata formats available for the item";
78 }
79 else if(errorType == "badResumptionToken"){
80 description = "The value of the resumptionToken argument is invalid or expired";
81 }
82 else if(errorType == "noSetHierarchy"){
83 description = "The repository does not support sets";
84 }
85
86 output << " <error code=\"" << errorType << "\">" << description << "</error>\n";
87}
88
89//----------------------------------------------------------------------------------------------
90
91text_t oaiaction::getName()
92{
93 return this->name;
94}
95
96//----------------------------------------------------------------------------------------------
97
98/**********
99 * Used in version 2.0 to provide the <datestamp> tag for each document. The function is passed
100 * the 'last modified' date of the file in epoch time (time in seconds since 1970-01-01 on Unix
101 * systems) and converts it to YYYY-MM-DD format.
102 */
103text_t oaiaction::parseDatestamp(time_t &rawtime)
104{
105 text_t year, month, day, lastModified;
106 tm *ptm;
107 ptm = gmtime(&rawtime);
108 int raw_month = ptm->tm_mon + 1;
109 int raw_day = ptm->tm_mday;
110
111 year = (ptm->tm_year + 1900); // Takes off 1900 for year by default, so replace it to get YYYY format
112
113 // Need the month in MM format, so if month is 1..9, add a 0 to the front
114 if(raw_month < 10){
115 month = "0";
116 month += raw_month;
117 }
118 else month = raw_month;
119
120 if(raw_day < 10){
121 day = "0";
122 day += raw_day;
123 }
124 else day = raw_day;
125
126 lastModified = year + "-" + month + "-" + day;
127
128 return lastModified;
129}
130
131//----------------------------------------------------------------------------------------------
132/**********
133 * Used by both versions to get the date & time of the client's request. The <responseDate> tag is
134 * in the format YYYY-MM-DDTHH:MM:SSZ, where T is simply the letter 'T' and Z is the local offset from
135 * GMT (now UTC). Examples of Z are +12:00 (NZ) or -03:00 for version 1.1. In version 2.0, the time
136 * is expected to be in UTC, and Z should simply be the character 'Z'.
137 */
138void oaiaction::getResponseDate(text_t &date)
139{
140 time_t rawtime;
141 tm *ptm;
142
143 time(&rawtime); // Get the epoch time
144
145 ptm = gmtime(&rawtime); // Convert to UTC-time formatted tm object
146
147 text_t month, day, hour, minute, second;
148 int raw_month = ptm->tm_mon + 1; // Note Jan = 0 ... Dec = 11, so add 1
149 int raw_day = ptm->tm_mday;
150 int raw_hour = ptm->tm_hour;
151 int raw_minute = ptm->tm_min;
152 int raw_second = ptm->tm_sec;
153
154 // Need the month in MM format, so if month is 1..9, add a 0 to the front
155 if(raw_month < 10){
156 month = "0";
157 }
158 month += raw_month;
159
160 // Same for days, hours, minutes and seconds
161 if(raw_day < 10){
162 day = "0";
163 }
164 day += raw_day;
165
166 if(raw_hour < 10){
167 hour = "0";
168 }
169 hour += raw_hour;
170
171 if(raw_minute < 10){
172 minute = "0";
173 }
174 minute += raw_minute;
175
176 if(raw_second < 10){
177 second = "0";
178 }
179 second += raw_second;
180
181 // Want YYYY-MM-DDTHH:MM:SS+UTC, where UTC is num hours from UTC(GMT) to localtime
182 date += (ptm->tm_year + 1900); // Takes off 1900 for year, so replace it to get YYYY format
183 date += "-";
184 date += month;
185 date += "-";
186 date += day;
187 date += "T";
188 date += hour;
189 date += ":";
190 date += minute;
191 date += ":";
192 date += second;
193 // If we're using v1.1, then tack on local time offset, otherwise don't
194 if(this->configuration->getOAIVersion() == 110){
195 date += _LOCALTIME_; // Defined in oaiaction.h. States how many hours localtime is from
196 // UTC (GMT), e.g. "+8:00", "-5:00"
197 }
198 else
199 date += "Z"; // If v2.0, we put 'Z' on the end rather than the localtime offset
200}
201
202//----------------------------------------------------------------------------------------------
203/**********
204 * Does different request tags depending on the version of the OAI protocol running
205 */
206void oaiaction::getRequestURL(oaiargs &params, text_t &requestURL)
207{
208 // Iterators for moving through the list of parameters (keys) specified
209 text_tmap::const_iterator here;
210 text_tmap::const_iterator end;
211 int numArgs = params.getSize();
212 text_t gsdlhome, httpdomain, httpprefix;
213
214 here = params.begin();
215 end = params.end();
216
217 // Read stuff from the gsdlsite.cfg file. We do this to get the base URL (httpdomain)
218 //site_cfg_read(gsdlhome, httpdomain, httpprefix);
219 text_t baseURL = this->configuration->getCollectionConfig("", "baseURL");
220
221 int version = this->configuration->getOAIVersion();
222
223 switch(version){
224 case 110:
225 /* Takes the form:
226 * <requestURL>http://baseURL.com/oaimain?verb="someVerb"&amp;key=value</requestURL>
227 */
228 requestURL = " <requestURL>" + baseURL;
229
230 if(numArgs == 0) break; // If no args, all done - the error will be picked up later
231
232 // The following lines will give us the "label=value" syntax
233 requestURL += "?";
234 requestURL += here->first;
235 requestURL += "=";
236 requestURL += html_safe(here->second); // parse the argument to ensure it is URL-encoding compliant
237 ++here;
238
239 while(here != end){
240 requestURL +="&amp;"; // Stick in the ampersand in URL encoding
241 requestURL += (here->first + "=" + html_safe(here->second));
242 ++here;
243 }
244 requestURL += "</requestURL>\n";
245 break;
246
247 case 200:
248 default:
249 /* Takes the form:
250 * <request verb="someVerb" key="value" key="value">
251 * http://baseURL.com/oaimain</request>
252 */
253 if(numArgs == 0) {
254 requestURL = " <request>" + baseURL + "</request>\n";
255 break;
256 }
257 requestURL = " <request " + here->first + "=\"" + here->second + "\"";
258 ++here;
259 while(here != end){
260 requestURL += (" " + here->first + "=\"" + here->second + "\"");
261 ++here;
262 }
263 requestURL += ">\n " + baseURL + "</request>\n";
264 break;
265 }
266}
267
268//----------------------------------------------------------------------------------------------
269/**********
270 * Send the (OAI version-dependent) response text to the output stream
271 */
272void oaiaction::getResponse(ostream &output, recptproto *protocol, oaiargs &params)
273{
274 bool error;
275 text_t date, requestURL;
276
277 // Write the response date & time into 'date'
278 this->getResponseDate(date);
279 int version = this->configuration->getOAIVersion();
280
281 // validate the action
282 error = !this->validateAction(protocol, params);
283
284 // raise an error for duplicated arguments and set the
285 // error type "manually" here...
286 if (params.hasDuplicateArg() && !error) {
287 this->errorType = "badArgument";
288 error = true;
289 }
290
291 // start with the required http header
292 if (version <= 110 && error){
293 output << "Status: 400 " << this->errorType << "\n";
294 output << "Content-Type: text/xml\n\n";
295 return;
296 }
297
298 output << "Status: 200\n";
299 output << "Content-Type: text/xml\n\n";
300
301 // output xml header parts
302 output << "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
303
304 if(version <= 110){
305 // output OAI v1.1 action header tag
306 output << "<" << this->name;
307 output << "\n xmlns=\"http://www.openarchives.com/OAI/1.1/OAI_" << this->name << "\" ";
308 output << "\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" ";
309 output << "\n xsi:schemaLocation=\"http://www.openarchives.org/OAI/1.1/OAI_" << this->name;
310 output << "\n http://www.openarchives.org/OAI/1.1/OAI_" << this->name << ".xsd\">\n";
311 }
312 else {
313 // output OAI v2.0 action header tag
314 output << "<OAI-PMH xmlns=\"http://www.openarchives.com/OAI/2.0\"\n"
315 << " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
316 << " xsi:schemaLocation=\"http://www.openarchives.org/OAI/2.0/\n"
317 << " http://www.openarchives.org/OAI/2.0/OAI-PMH.xsd\">\n";
318 }
319 // output current time for response
320 output << " <responseDate>" << date << "</responseDate>\n";
321
322 // output request URL. This differs depending on the OAI protocol version currently running, so
323 // the entire field - including tags - must be put into the text_t variable by getRequestURL()
324 this->getRequestURL(params, requestURL);
325
326 output << requestURL ;
327
328 if (error == false) {
329 // a string stream to write the content of the action to; this is done so that we can
330 // avoid outputting the action tag if the action's body is unsuccessful, in which
331 // case the leading tag must be suppressed
332#if defined(GSDL_USE_IOS_H)
333 ostrstream outstream;
334#else
335 ostringstream outstream;
336#endif
337
338 // Version 2.0 needs an <Identify>, etc. tag after the OAI-PMH header, IF there is no error
339 //
340 // An action that outputs no content should raise an error state to suppress the
341 // matching opening and close tags if it outputs no content in OAI 2.0
342 error = !this->output_content(outstream, protocol, params);
343
344 // output the leading tag if no error occurred
345 if (error == false) {
346 if (version >= 200) {
347 this->output_action_tag(output, true);
348 }
349 }
350
351 // now output the body of the action content
352#if defined(GSDL_USE_IOS_H)
353 outstream << ends; // Ensure outstream is null-terminated correctly
354#endif
355 output << outstream.str();
356 }
357 else {
358 if (version >= 200) {
359 this->output_error(output, this->errorType);
360 }
361 }
362
363 // close out our response - both versions need this line, but v2.0 only needs it if there was no error
364 if((version == 110) || (version >= 200 && error == false)){
365 this->output_action_tag(output, false);
366 }
367 if(version >= 200){
368 output << "</OAI-PMH>\n";
369 }
370}
371
372void oaiaction::output_action_tag(ostream &output, bool openTag)
373{
374 output << " <";
375 if (!openTag) {
376 output << "/";
377 }
378 output << this->name << ">" << endl;
379}
380
381void oaiaction::output_record_header(ostream &output, const text_t &oaiLabel, const text_t &lastModified,
382 const text_tarray &memberOf, int oaiVersion)
383{
384 output << " <header>" << endl;
385 output << " <identifier>" << oaiLabel << "</identifier>" << endl;
386 output << " <datestamp>" << lastModified << "</datestamp>" << endl;
387
388 // copy the collection name off the front of oaiLabel
389 text_t::const_iterator colon = findchar(oaiLabel.begin(), oaiLabel.end(), ':');
390 text_t collection = substr(oaiLabel.begin(), colon);
391
392 if(oaiVersion >= 200){
393 text_tarray::const_iterator member = memberOf.begin();
394 text_tarray::const_iterator memEnd = memberOf.end();
395
396 // As well as all the subsets that a doc appears in, it is also a member of the 'collection' set
397 output << " <setSpec>" << collection << "</setSpec>" << endl;
398 while (member != memEnd) {
399 text_t oaiSet = *member;
400 oaiclassifier::toOAI(collection, oaiSet);
401 output << " <setSpec>" << oaiSet << "</setSpec>" << endl;
402 ++member;
403 }
404 }
405 output << " </header>" << endl;
406}
407
408void oaiaction::getLastModifiedDate(ResultDocInfo_t &doc_info, text_t &lastModified)
409{
410 text_t temp;
411
412
413 MetadataInfo_tmap::iterator current = doc_info.metadata.begin();
414 MetadataInfo_tmap::iterator end = doc_info.metadata.end();
415
416 while(current != end){
417 temp = current->first;
418 lc(temp);
419 if(temp == "dc.date"){
420 lastModified = current->second.values[0];
421 return;
422 }
423 else{
424 if (temp == "lastmodified" && lastModified == "" && current->second.values.size() >= 1) {
425 lastModified = current->second.values[0];
426 time_t raw_time = (time_t)lastModified.getint();
427 lastModified = this->parseDatestamp(raw_time);
428 return;
429 }
430 }
431 ++current;
432 }
433}
434
435bool oaiaction::inDateRange(const text_t &from, const text_t &until, const text_t &collection,
436 const text_t &OID, recptproto *protocol, ostream &logout)
437{
438 FilterResponse_t response;
439 text_tset metadata;
440 bool status_ok = get_info(OID, collection, "", metadata, false, protocol, response, logout);
441 bool not_too_early = false, not_too_recent = false;
442
443 if(status_ok) {
444 ResultDocInfo_t doc_info = response.docInfo[0];
445 text_t lastModDate;
446 this->getLastModifiedDate(doc_info, lastModDate);
447
448 // All date fields should be in the form YYYY-MM-DD, which allows for a direct comparison
449 if(from != ""){
450 if(from <= lastModDate)
451 not_too_early = true;
452 }
453 else
454 not_too_early = true; // If there's no FROM field, then the record can't be too early
455
456 if(until != ""){
457 if(lastModDate <= until)
458 not_too_recent = true;
459 }
460 else
461 not_too_recent = true; // If there's no UNTIL field, then the record can't be too recent
462
463 if(not_too_early && not_too_recent)
464 return true;
465 else
466 return false;
467 }
468 else
469 return false;
470}
Note: See TracBrowser for help on using the repository browser.