source: gsdl/trunk/src/oaiservr/oaiaction.cpp@ 15192

Last change on this file since 15192 was 15192, checked in by mdewsnip, 16 years ago

Replaced all instances of "openarchives.com" with "openarchives.com", so the schema validation works. By DL Consulting Ltd.

  • 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.org/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.org/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.