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

Last change on this file since 8223 was 8182, checked in by cs025, 20 years ago

Added OAI Server code to Greenstone

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