root/gsdl/trunk/runtime-src/src/oaiservr/oaiaction.cpp @ 19124

Revision 19124, 14.6 KB (checked in by kjdon, 10 years ago)

updated the location of the stylesheet

  • Property svn:keywords set to Author Date Id Revision
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  // 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 == "dc.date"){
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 browser.