source: gsdl/trunk/runtime-src/src/oaiservr/oaiaction.cpp@ 18862

Last change on this file since 18862 was 18862, checked in by kjdon, 15 years ago

add gsdl_qdc to list of acceptable meta formats. Output xml-stylesheet line to use the oai2.xsl file to display reponses nicely in browser

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