source: main/trunk/greenstone2/runtime-src/src/oaiservr/oaiaction.cpp@ 21883

Last change on this file since 21883 was 21883, checked in by kjdon, 11 years ago

don't use dc.date for datestamp, instead use the new gs.OAIDateStamp

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