/* The contents of this file are subject to the license and copyright terms * detailed in the license directory at the root of the source tree (also * available online at http://fedora-commons.org/license/). */ package fedora.oai; import java.io.BufferedWriter; import java.io.File; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TimeZone; import fedora.common.Constants; import fedora.server.Context; import fedora.server.Server; import fedora.server.errors.authorization.AuthzException; import fedora.server.errors.authorization.AuthzOperationalException; import fedora.server.security.Authorization; /** * OAIResponder. * * @author Chris Wilper */ public class OAIResponder implements Constants { private final OAIProvider m_provider; private DateGranularitySupport m_granularity; private Authorization m_authorization; public OAIResponder(OAIProvider provider) { m_provider = provider; } public void respond(Context context, Map args, OutputStream outStream) throws RepositoryException, AuthzException { if (m_authorization == null) { Server server; try { server = Server.getInstance(new File(FEDORA_HOME)); } catch (Throwable e) { throw new AuthzOperationalException("couldn't attempt authz", e); } m_authorization = (Authorization) server .getModule("fedora.server.security.Authorization"); } m_authorization.enforceOAIRespond(context); m_granularity = m_provider.getDateGranularitySupport(); PrintWriter out = null; try { out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(outStream, "UTF-8"))); } catch (UnsupportedEncodingException uee) { // not going to happen... all java impls support UTF-8 } String verb = (String) args.get("verb"); String baseURL = m_provider .getBaseURL(context .getEnvironmentValue(HTTP_REQUEST.SECURITY.uri) .equals(HTTP_REQUEST.SECURE.uri) ? "https" : "http", context .getEnvironmentValue(HTTP_REQUEST.SERVER_PORT.uri)); try { if (verb == null) { throw new BadVerbException("Request did not specify a verb."); } if (verb.equals("GetRecord")) { String identifier = (String) args.get("identifier"); if (identifier == null) { throw new BadArgumentException("GetRecord request did not specify 'identifier' argument."); } String metadataPrefix = (String) args.get("metadataPrefix"); if (metadataPrefix == null) { throw new BadArgumentException("GetRecord request did not specify 'metadataPrefix' argument."); } if (args.size() > 3) { throw new BadArgumentException("GetRecord request specified illegal argument(s)."); } Record record = m_provider.getRecord(identifier, metadataPrefix); respondToGetRecord(args, baseURL, record, out); } else if (verb.equals("Identify")) { if (args.size() > 1) { throw new BadArgumentException("Identify request specified illegal argument(s)."); } String repositoryName = m_provider.getRepositoryName(); String protocolVersion = m_provider.getProtocolVersion(); Date earliestDatestamp = m_provider.getEarliestDatestamp(); DeletedRecordSupport deletedRecord = m_provider.getDeletedRecordSupport(); Set adminEmails = m_provider.getAdminEmails(); Set compressions = m_provider.getSupportedCompressionEncodings(); Set descriptions = m_provider.getDescriptions(); respondToIdentify(args, baseURL, repositoryName, protocolVersion, earliestDatestamp, deletedRecord, adminEmails, compressions, descriptions, out); } else if (verb.equals("ListIdentifiers")) { String rToken = (String) args.get("resumptionToken"); List headers; if (rToken != null) { if (args.size() > 2) { throw new BadArgumentException("ListIdentifiers request specified resumptionToken with other arguments."); } headers = m_provider.getHeaders(rToken); } else { Iterator iter = args.keySet().iterator(); boolean badParam = false; Date from = null; Date until = null; String metadataPrefix = null; String set = null; while (iter.hasNext()) { String name = (String) iter.next(); if (name.equals("metadataPrefix")) { metadataPrefix = (String) args.get(name); } else if (name.equals("set")) { set = (String) args.get(name); } else if (name.equals("from")) { from = getUTCDate((String) args.get(name), false); } else if (name.equals("until")) { until = getUTCDate((String) args.get(name), true); } else if (!name.equals("verb")) { badParam = true; } } if (from != null && until != null) { assertSameGranularity((String) args.get("from"), (String) args.get("until")); } if (badParam) { throw new BadArgumentException("ListIdentifiers request specified illegal argument(s)."); } if (metadataPrefix == null) { throw new BadArgumentException("ListIdentifiers request did not specify metadataPrefix argument."); } headers = m_provider.getHeaders(from, until, metadataPrefix, set); } if (headers.size() == 0) { throw new NoRecordsMatchException("No records match the providied criteria."); } ResumptionToken resumptionToken = null; if (m_provider.getMaxHeaders() > 0) { if (headers.size() > m_provider.getMaxHeaders()) { resumptionToken = (ResumptionToken) headers .get(headers.size() - 1); headers = headers.subList(0, headers.size() - 1); } } respondToListIdentifiers(args, baseURL, headers, resumptionToken, out); } else if (verb.equals("ListMetadataFormats")) { String identifier = (String) args.get("identifier"); if (identifier == null) { if (args.size() > 1) { throw new BadArgumentException("ListMetadataFormats request specified illegal argument(s)."); } } else { if (args.size() > 2) { throw new BadArgumentException("ListMetadataFormats request specified illegal argument(s)."); } } respondToListMetadataFormats(args, baseURL, m_provider .getMetadataFormats(identifier), out); } else if (verb.equals("ListRecords")) { String rToken = (String) args.get("resumptionToken"); List records; if (rToken != null) { if (args.size() > 2) { throw new BadArgumentException("ListRecords request specified resumptionToken with other arguments."); } records = m_provider.getRecords(rToken); } else { Iterator iter = args.keySet().iterator(); boolean badParam = false; Date from = null; Date until = null; String metadataPrefix = null; String set = null; while (iter.hasNext()) { String name = (String) iter.next(); if (name.equals("metadataPrefix")) { metadataPrefix = (String) args.get(name); } else if (name.equals("set")) { set = (String) args.get(name); } else if (name.equals("from")) { from = getUTCDate((String) args.get(name), false); } else if (name.equals("until")) { until = getUTCDate((String) args.get(name), true); } else if (!name.equals("verb")) { badParam = true; } } if (from != null && until != null) { assertSameGranularity((String) args.get("from"), (String) args.get("until")); } if (badParam) { throw new BadArgumentException("ListRecords request specified illegal argument(s)."); } if (metadataPrefix == null) { throw new BadArgumentException("ListRecords request did not specify metadataPrefix argument."); } records = m_provider.getRecords(from, until, metadataPrefix, set); } if (records.size() == 0) { throw new NoRecordsMatchException("No records match the providied criteria."); } ResumptionToken resumptionToken = null; if (m_provider.getMaxRecords() > 0) { if (records.size() > m_provider.getMaxRecords()) { resumptionToken = (ResumptionToken) records .get(records.size() - 1); records = records.subList(0, records.size() - 1); } } respondToListRecords(args, baseURL, records, resumptionToken, out); } else if (verb.equals("ListSets")) { String rToken = (String) args.get("resumptionToken"); List sets; if (rToken == null) { if (args.size() > 1) { throw new BadArgumentException("ListSets request specified illegal argument(s)."); } sets = m_provider.getSets(); } else { if (args.size() > 2) { throw new BadArgumentException("ListSets request specified illegal argument(s)."); } sets = m_provider.getSets(rToken); } ResumptionToken resumptionToken = null; if (m_provider.getMaxSets() > 0) { if (sets.size() > m_provider.getMaxSets()) { resumptionToken = (ResumptionToken) sets.get(sets.size() - 1); sets = sets.subList(0, sets.size() - 1); } } respondToListSets(args, baseURL, sets, resumptionToken, out); } else { throw new BadVerbException("Unrecognized verb, '" + verb + "'."); } } catch (OAIException oaie) { respondWithError(oaie, verb, baseURL, out); } finally { out.flush(); } } private void assertSameGranularity(String from, String until) throws BadArgumentException { if (from.endsWith("Z") && !until.endsWith("Z") || until.endsWith("Z") && !from.endsWith("Z")) { throw new BadArgumentException("Date granularities of from and until arguments do not match."); } } private void respondToGetRecord(Map args, String baseURL, Record record, PrintWriter out) { appendTop(out); appendRequest(args, baseURL, out); out.println(" <GetRecord>"); appendRecord(" ", record, out); out.println(" </GetRecord>"); appendBottom(out); } private void respondToIdentify(Map args, String baseURL, String repositoryName, String protocolVersion, Date earliestDatestamp, DeletedRecordSupport deletedRecord, Set adminEmails, Set compressions, Set descriptions, PrintWriter out) { appendTop(out); appendRequest(args, baseURL, out); out.println(" <Identify>"); out.println(" <repositoryName>" + OAIResponder.enc(repositoryName) + "</repositoryName>"); out.println(" <baseURL>" + OAIResponder.enc(baseURL) + "</baseURL>"); out.println(" <protocolVersion>" + protocolVersion + "</protocolVersion>"); Iterator iter = adminEmails.iterator(); while (iter.hasNext()) { out.println(" <adminEmail>" + OAIResponder.enc((String) iter.next()) + "</adminEmail>"); } out.println(" <earliestDatestamp>" + getUTCString(earliestDatestamp, m_granularity == DateGranularitySupport.SECONDS) + "</earliestDatestamp>"); out.println(" <deletedRecord>" + deletedRecord.toString() + "</deletedRecord>"); out.println(" <granularity>" + m_granularity.toString() + "</granularity>"); iter = compressions.iterator(); while (iter.hasNext()) { out .println(" <compression>" + OAIResponder.enc((String) iter.next()) + "</compression>"); } iter = descriptions.iterator(); while (iter.hasNext()) { out.println(" <description>"); out.println((String) iter.next()); out.println(" </description>"); } out.println(" </Identify>"); appendBottom(out); } // resumptionToken may be null private void respondToListIdentifiers(Map args, String baseURL, List headers, ResumptionToken resumptionToken, PrintWriter out) { appendTop(out); appendRequest(args, baseURL, out); out.println(" <ListIdentifiers>"); for (int i = 0; i < headers.size(); i++) { Header header = (Header) headers.get(i); appendHeader(" ", header, out); } appendResumptionToken(resumptionToken, out); out.println(" </ListIdentifiers>"); appendBottom(out); } private void respondToListMetadataFormats(Map args, String baseURL, Set metadataFormats, PrintWriter out) { appendTop(out); appendRequest(args, baseURL, out); out.println(" <ListMetadataFormats>"); Iterator iter = metadataFormats.iterator(); while (iter.hasNext()) { MetadataFormat f = (MetadataFormat) iter.next(); out.println(" <metadataFormat>"); out.println(" <metadataPrefix>" + f.getPrefix() + "</metadataPrefix>"); out.println(" <schema>" + f.getSchemaLocation() + "</schema>"); out.println(" <metadataNamespace>" + f.getNamespaceURI() + "</metadataNamespace>"); out.println(" </metadataFormat>"); } out.println(" </ListMetadataFormats>"); appendBottom(out); } // resumptionToken may be null private void respondToListRecords(Map args, String baseURL, List records, ResumptionToken resumptionToken, PrintWriter out) { appendTop(out); appendRequest(args, baseURL, out); out.println(" <ListRecords>"); for (int i = 0; i < records.size(); i++) { appendRecord(" ", (Record) records.get(i), out); } appendResumptionToken(resumptionToken, out); out.println(" </ListRecords>"); appendBottom(out); } // resumptionToken may be null private void respondToListSets(Map args, String baseURL, List sets, ResumptionToken resumptionToken, PrintWriter out) { appendTop(out); appendRequest(args, baseURL, out); out.println(" <ListSets>"); for (int i = 0; i < sets.size(); i++) { SetInfo s = (SetInfo) sets.get(i); out.println(" <set>"); out.println(" <setSpec>" + s.getSpec() + "</setSpec>"); out.println(" <setName>" + s.getName() + "</setName>"); Iterator iter = s.getDescriptions().iterator(); while (iter.hasNext()) { out.println(" <setDescription>\n"); out.println((String) iter.next()); out.println(" </setDescription>\n"); } out.println(" </set>"); } appendResumptionToken(resumptionToken, out); out.println(" </ListSets>"); appendBottom(out); } private void appendRecord(String indent, Record record, PrintWriter out) { Header header = record.getHeader(); String metadata = record.getMetadata(); Set abouts = record.getAbouts(); out.println(indent + "<record>"); appendHeader(indent + " ", header, out); if (header.isAvailable()) { out.println(indent + " <metadata>"); out.println(metadata); out.println(indent + " </metadata>"); Iterator iter = abouts.iterator(); while (iter.hasNext()) { out.println(indent + " <about>"); out.println((String) iter.next()); out.println(indent + " </about>"); } } out.println(indent + "</record>"); } private void appendHeader(String indent, Header header, PrintWriter out) { String identifier = header.getIdentifier(); Date datestamp = header.getDatestamp(); Set setSpecs = header.getSetSpecs(); boolean isAvailable = header.isAvailable(); out.print(indent + "<header"); if (!isAvailable) { out.print(" status=\"deleted\""); } out.println(">"); out.println(indent + " <identifier>" + OAIResponder.enc(identifier) + "</identifier>"); out.println(indent + " <datestamp>" + getUTCString(datestamp, m_granularity == DateGranularitySupport.SECONDS) + "</datestamp>"); Iterator iter = setSpecs.iterator(); while (iter.hasNext()) { out.println(indent + " <setSpec>" + OAIResponder.enc((String) iter.next()) + "</setSpec>"); } out.println(indent + "</header>"); } private void appendResumptionToken(ResumptionToken token, PrintWriter out) { if (token != null) { out.print(" <resumptionToken"); if (token.getExpirationDate() != null) { out.print(" expirationDate=\"" + getUTCString(token.getExpirationDate(), true) + "\""); } if (token.getCompleteListSize() >= 0) { out.print(" completeListSize=\"" + token.getCompleteListSize() + "\""); } if (token.getCursor() >= 0) { out.print(" cursor=\"" + token.getCursor() + "\""); } if (token.getValue() != null) { out.println(">" + token.getValue() + "</resumptionToken>"); } else { out.println("/>"); } } } private void appendRequest(Map args, String baseURL, PrintWriter out) { out.print(" <request"); Iterator iter = args.keySet().iterator(); while (iter.hasNext()) { String name = (String) iter.next(); String value = (String) args.get(name); out.print(" " + name + "=\"" + OAIResponder.enc(value) + "\""); } out.println(">" + OAIResponder.enc(baseURL) + "</request>"); } private void appendTop(PrintWriter out) { out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); out.println("<OAI-PMH xmlns=\"" + OAI_PMH.uri + "\""); out.println(" xmlns:xsi=\"" + XSI.uri + "\""); out.println(" xsi:schemaLocation=\"" + OAI_PMH.uri + " " + OAI_PMH2_0.xsdLocation + "\">"); out.println(" <responseDate>" + getUTCString(getUTCDate(new Date()), true) + "</responseDate>"); } private void appendBottom(PrintWriter out) { out.println("</OAI-PMH>"); } private void respondWithError(OAIException e, String verb, String baseURL, PrintWriter out) { appendTop(out); out.print(" <request"); if (!e.getCode().equals("badVerb")) { out.print(" verb=\"" + verb + "\""); } out.println(">" + OAIResponder.enc(baseURL) + "</request>"); if (e.getMessage() == null) { out.println(" <error code=\"" + e.getCode() + "\"/>"); } else { out.println(" <error code=\"" + e.getCode() + "\">" + OAIResponder.enc(e.getMessage()) + "</error>"); } appendBottom(out); } private static String getUTCString(Date utcDate, boolean fineGranularity) { SimpleDateFormat formatter; if (fineGranularity) { formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); } else { formatter = new SimpleDateFormat("yyyy-MM-dd"); } return formatter.format(utcDate); } private Date getUTCDate(String formattedDate, boolean isUntil) throws BadArgumentException { if (formattedDate == null) { return null; } try { if (formattedDate.endsWith("Z")) { if (m_granularity == DateGranularitySupport.SECONDS) { SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); return formatter.parse(formattedDate); } else { throw new BadArgumentException("Repository does not support that granularity."); } } else { SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); if (isUntil) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); return sdf.parse(formatter.format(formatter .parse(formattedDate)) + "T23:59:59Z"); } else { return formatter.parse(formattedDate); } } } catch (ParseException pe) { throw new BadArgumentException("Error parsing date."); } } private Date getUTCDate(Date localDate) { Calendar cal = Calendar.getInstance(); int tzOffset = cal.get(Calendar.ZONE_OFFSET); TimeZone tz = cal.getTimeZone(); if (tz.inDaylightTime(localDate)) { tzOffset += cal.get(Calendar.DST_OFFSET); } Date UTCDate = new Date(); UTCDate.setTime(localDate.getTime() + tzOffset); return UTCDate; } /** * Returns an XML-appropriate encoding of the given String. * * @param in * The String to encode. * @return A new, encoded String. */ private static String enc(String in) { StringBuffer out = new StringBuffer(); enc(in, out); return out.toString(); } /** * Appends an XML-appropriate encoding of the given String to the given * StringBuffer. * * @param in * The String to encode. * @param buf * The StringBuffer to write to. */ private static void enc(String in, StringBuffer out) { for (int i = 0; i < in.length(); i++) { enc(in.charAt(i), out); } } /** * Appends an XML-appropriate encoding of the given range of characters to * the given StringBuffer. * * @param in * The char buffer to read from. * @param start * The starting index. * @param length * The number of characters in the range. * @param out * The StringBuffer to write to. */ private static void enc(char[] in, int start, int length, StringBuffer out) { for (int i = start; i < length + start; i++) { enc(in[i], out); } } /** * Appends an XML-appropriate encoding of the given character to the given * StringBuffer. * * @param in * The character. * @param out * The StringBuffer to write to. */ private static void enc(char in, StringBuffer out) { if (in == '&') { out.append("&"); } else if (in == '<') { out.append("<"); } else if (in == '>') { out.append(">"); } else if (in == '\"') { out.append("""); } else if (in == '\'') { out.append("'"); } else { out.append(in); } } }