Commit 6dd78548 authored by Steinberg, Jan's avatar Steinberg, Jan

exeley specialties and test

parent 58bd1ded
......@@ -46,318 +46,323 @@ import org.xml.sax.InputSource;
import net.sf.saxon.TransformerFactoryImpl;
import net.sf.saxon.trans.XPathException;
public class XsltTransformerOaiPmhBundlesStreamSource implements BundlesStreamSource, ErrorListener {
private final static Logger LOG = LoggerFactory.getLogger(XsltTransformerOaiPmhBundlesStreamSource.class);
private String oaiPmhEndpoint;
private String setSpec;
private Map<String, String> metadataPrefix2XsltMap;
private OaiPmhClient client;
HarvestingIntervalType intervalType;
private LocalDate dayFrom, dayUntil;
private Instant secondFrom, secondUntil;
public XsltTransformerOaiPmhBundlesStreamSource(String oaiPmhEndpoint, Map<String, String> metadataPrefix2XsltMap) {
this.oaiPmhEndpoint = oaiPmhEndpoint;
this.metadataPrefix2XsltMap = metadataPrefix2XsltMap;
this.intervalType = HarvestingIntervalType.FULL_HARVEST;
client = new OaiPmhClient(oaiPmhEndpoint);
}
public XsltTransformerOaiPmhBundlesStreamSource(String oaiPmhEndpoint, Map<String, String> metadataPrefix2XsltMap, LocalDate from, LocalDate until) {
this(oaiPmhEndpoint, metadataPrefix2XsltMap);
this.intervalType = HarvestingIntervalType.DAY_INTERVAL_HARVEST;
this.dayFrom = from;
this.dayUntil = until;
}
public XsltTransformerOaiPmhBundlesStreamSource(String oaiPmhEndpoint, Map<String, String> metadataPrefix2XsltMap, Instant from, Instant until) {
this(oaiPmhEndpoint, metadataPrefix2XsltMap);
this.intervalType = HarvestingIntervalType.SECOND_INTERVAL_HARVEST;
this.secondFrom = from;
this.secondUntil = until;
}
// And here come the same methods but with set specification
public XsltTransformerOaiPmhBundlesStreamSource(String oaiPmhEndpoint, String setSpec, Map<String, String> metadataPrefix2XsltMap) {
this.oaiPmhEndpoint = oaiPmhEndpoint;
this.setSpec = setSpec;
this.metadataPrefix2XsltMap = metadataPrefix2XsltMap;
this.intervalType = HarvestingIntervalType.FULL_HARVEST;
client = new OaiPmhClient(oaiPmhEndpoint);
}
public XsltTransformerOaiPmhBundlesStreamSource(String oaiPmhEndpoint, String setSpec, Map<String, String> metadataPrefix2XsltMap, LocalDate from, LocalDate until) {
this(oaiPmhEndpoint, setSpec, metadataPrefix2XsltMap);
this.intervalType = HarvestingIntervalType.DAY_INTERVAL_HARVEST;
this.dayFrom = from;
this.dayUntil = until;
}
public XsltTransformerOaiPmhBundlesStreamSource(String oaiPmhEndpoint, String setSpec, Map<String, String> metadataPrefix2XsltMap, Instant from, Instant until) {
this(oaiPmhEndpoint, setSpec, metadataPrefix2XsltMap);
this.intervalType = HarvestingIntervalType.SECOND_INTERVAL_HARVEST;
this.secondFrom = from;
this.secondUntil = until;
}
@Override
public Stream<Bundle> getBundlesStream() {
Stream<Bundle> resultBundleStream;
Set<String> metadataPrefixes = metadataPrefix2XsltMap.keySet();
final String from;
final String until;
switch (intervalType) {
case FULL_HARVEST:
from = null;
until = null;
break;
case SECOND_INTERVAL_HARVEST:
from = convertInstantToOaiPmhString(secondFrom);
until = convertInstantToOaiPmhString(secondUntil);
break;
case DAY_INTERVAL_HARVEST:
from = convertLocalDateToOaiPmhString(dayFrom);
until = convertLocalDateToOaiPmhString(dayUntil);
break;
default:
from = null;
until = null;
break;
}
// some record's metadata may not be available for all specified metadataPrefixes
// therefore collecting the union of all identifiers over all specified metadataPrefixes
Stream<OAIPMHtype> listIdentifiersResponseStream = metadataPrefixes.
stream().
flatMap(mp -> {
LOG.debug("filling list identifier stream with mp {}, from {}, until {}, setSpec {}", mp, from, until, setSpec);
if (setSpec == null|| setSpec.trim().isEmpty()) {
return client.listIdentifiersStream(mp, from, until, null);
}
return client.listIdentifiersStream(mp, from, until, setSpec);
});
Stream<String> uniqueIdentifiersStream = listIdentifiersResponseStream.flatMap( oaiPmhType -> {
/*return oaiPmhType.
getListIdentifiers().
getHeader().
stream().map(h -> h.getIdentifier() );
*/
Stream<String> result = Stream.empty();
ListIdentifiersType listIdentifiersType = oaiPmhType.getListIdentifiers();
if (null != listIdentifiersType) {
List<HeaderType> headers = listIdentifiersType.getHeader();
if (null != headers) {
result = headers.stream().map(h -> h.getIdentifier() );
}
}
return result;
}).
filter(Objects::nonNull).
distinct();
List<String> uniqueIdentifiers = uniqueIdentifiersStream.collect( Collectors.toList() );
//uniqueIdentifiers.forEach(LOG::debug);
resultBundleStream = uniqueIdentifiers.stream().map(this::getBundle).filter(Objects::nonNull);
return resultBundleStream;
}
public Bundle getBundle(String oaiPmhIdentifier) {
LOG.debug("GetBundle - {}", oaiPmhIdentifier);
//Bundle bundleResult = new AutonomouslyContentResolvingBundle(ImmutableSet.of() );
Set<Metadatum> bundleMetadata = new HashSet<>();
Set<String> metadataPrefixes = metadataPrefix2XsltMap.keySet();
String lastModifiedString = "";
Bundle resultBundle = null;
//LOG.info("looking up {}", id);
for (String metadataPrefix : metadataPrefixes) {
try {
// --- STEP 1: get XML input
String getRecordXmlDocumentResponseString = client.getRecordString(oaiPmhIdentifier, metadataPrefix);
// try to make exeley data transformable ( & -> &amp; to start with)
// ToDo: find the right location for it!
if ( getRecordXmlDocumentResponseString.contains("identifier=\"oai:exeley.com:10.") ) {
getRecordXmlDocumentResponseString = getRecordXmlDocumentResponseString.replace("&", "&amp;");
}
//LOG.info("--------------------------");
//LOG.info("{}", getRecordXmlDocumentResponseString);
if ( isDeletedRecord(getRecordXmlDocumentResponseString) ) {
resultBundle = null;
break;
}
else {
// --- STEP 2: convert XML input according to XSLT
TransformerFactory factory = TransformerFactory.newInstance("net.sf.saxon.TransformerFactoryImpl", TransformerFactoryImpl.class.getClassLoader() );
Templates xslTemplate = factory.newTemplates(
new StreamSource(
new StringReader(
metadataPrefix2XsltMap.
get(metadataPrefix) ) ) );
Source xmlInput = new StreamSource( new StringReader(getRecordXmlDocumentResponseString) );
StringWriter writer = new StringWriter();
Result xmlOutput = new StreamResult(writer);
Transformer transformer = xslTemplate.newTransformer();
transformer.setErrorListener(this);
transformer.transform(xmlInput, xmlOutput);
String xsltConvertedXmlOutput = writer.toString();
// LOG.info("---CONVERTED-----------------------");
// LOG.info("{}", output);
// STEP 3: convert XSLT-converted XML output to Java JAXB object
JAXBContext jaxbContext = JAXBContext.newInstance(XmlBundle.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
InputStream convertedXmlInputStream = new ByteArrayInputStream(xsltConvertedXmlOutput.getBytes(StandardCharsets.UTF_8) );
Bundle currentPartBundle = (Bundle) unmarshaller.unmarshal(convertedXmlInputStream);
Set<Metadatum> metadata = currentPartBundle.getMetadata();
bundleMetadata.addAll(metadata);
// STEP 4: extract lastModifiedDate
StringReader sr = new StringReader(getRecordXmlDocumentResponseString);
//LOG.info("sr=\n{}", getRecordXmlDocumentResponseString);
JAXBContext oaiPmhjaxbContext = JAXBContext.newInstance(OAIPMHtype.class);
Unmarshaller oaiPmhUnmarshaller = oaiPmhjaxbContext.createUnmarshaller();
@SuppressWarnings("unchecked")
JAXBElement<OAIPMHtype> wrappedResponseObject = (JAXBElement<OAIPMHtype>) oaiPmhUnmarshaller.unmarshal(sr);
OAIPMHtype response = wrappedResponseObject.getValue();
lastModifiedString = response.getGetRecord().getRecord().getHeader().getDatestamp();
// STEP 5: add identifier metadatum
Metadatum reference = new SimpleMetadatum("internal.dda.reference", oaiPmhEndpoint + "@@" + oaiPmhIdentifier);
bundleMetadata.add(reference);
resultBundle = BundleBuilder.create().withMetadata(bundleMetadata).withLastModifiedString(lastModifiedString).build();
}
}
catch (XPathException e) {
LOG.debug("Catched XPathException");
String errorCode = e.getErrorCodeLocalPart();
if (null != errorCode) {
if ("filteraway".equals(errorCode) ) {
LOG.info("1- filtering away oaiPmhIdentifier={}", oaiPmhIdentifier);
LOG.info("Error detail: {}", e.getCause().getLocalizedMessage());
resultBundle = null;
// test if this break is really necessary, taken out. STJ
// break;
}
else {
LOG.warn("2- fatalError. Filtering away oaiPmhIdentifier=" + oaiPmhIdentifier, e);
}
}
else {
LOG.warn("3- fatalError. Filtering away oaiPmhIdentifier=" + oaiPmhIdentifier, e);
}
resultBundle = null;
}
catch (Throwable t) {
LOG.warn("Problem getting record with id " + oaiPmhIdentifier + " and metadataPrefix " + metadataPrefix + ". Skipping it.", t);
resultBundle = null;
}
}
return resultBundle;
}
public static boolean isDeletedRecord(String getRecordXmlDocumentResponseString) {
XPath xpath = XPathFactory.newInstance().newXPath();
InputSource inputSource = new InputSource( new StringReader(getRecordXmlDocumentResponseString) );
String headerStatus;
boolean result;
try {
//see https://stackoverflow.com/a/6397369/923560
XPathExpression expr = xpath.compile(
"//*[local-name()='OAI-PMH' and namespace-uri()='http://www.openarchives.org/OAI/2.0/']"
+ "/*[local-name()='GetRecord' and namespace-uri()='http://www.openarchives.org/OAI/2.0/']"
+ "/*[local-name()='record' and namespace-uri()='http://www.openarchives.org/OAI/2.0/']"
+ "/*[local-name()='header' and namespace-uri()='http://www.openarchives.org/OAI/2.0/']/"
+ "@status");
headerStatus = expr.evaluate(inputSource);
if ( "deleted".equals(headerStatus) ) {
result = true;
}
else {
result = false;
}
}
catch (XPathExpressionException e) {
LOG.error("Problem identifying if deleted record", e);
result = true;
}
return result;
}
@Override
public String getReference() {
return oaiPmhEndpoint;
}
public String getSetSpec() {
return setSpec;
}
@Override
public Set<Metadatum> getAllMetadata(String scopedIdentifier) {
return getBundle(scopedIdentifier).getMetadata();
}
public static String convertInstantToOaiPmhString(Instant instant) {
String result;
result = instant != null? instant.toString() : null;
return result;
}
public static String convertLocalDateToOaiPmhString(LocalDate localDate) {
String result;
result = localDate != null? localDate.toString() : null;
return result;
}
@Override
public void error(TransformerException arg0) throws TransformerException {
LOG.error("error", arg0);
}
@Override
public void fatalError(TransformerException arg0) throws TransformerException {
if (arg0 instanceof XPathException) {
XPathException xPathException = (XPathException) arg0;
String errorCode = xPathException.getErrorCodeLocalPart();
if (null != errorCode) {
if (! "filteraway".equals(errorCode) ) {
LOG.error("fatalError", arg0);
}
}
else {
LOG.error("fatalError", arg0);
}
}
else {
LOG.error("fatalError", arg0);
}
}
@Override
public void warning(TransformerException arg0) throws TransformerException {
LOG.warn("warn", arg0);
}
private final static Logger LOG = LoggerFactory.getLogger(XsltTransformerOaiPmhBundlesStreamSource.class);
private String oaiPmhEndpoint;
private String setSpec;
private Map<String, String> metadataPrefix2XsltMap;
private OaiPmhClient client;
HarvestingIntervalType intervalType;
private LocalDate dayFrom, dayUntil;
private Instant secondFrom, secondUntil;
public XsltTransformerOaiPmhBundlesStreamSource(String oaiPmhEndpoint, Map<String, String> metadataPrefix2XsltMap) {
this.oaiPmhEndpoint = oaiPmhEndpoint;
this.metadataPrefix2XsltMap = metadataPrefix2XsltMap;
this.intervalType = HarvestingIntervalType.FULL_HARVEST;
client = new OaiPmhClient(oaiPmhEndpoint);
}
public XsltTransformerOaiPmhBundlesStreamSource(String oaiPmhEndpoint, Map<String, String> metadataPrefix2XsltMap,
LocalDate from, LocalDate until) {
this(oaiPmhEndpoint, metadataPrefix2XsltMap);
this.intervalType = HarvestingIntervalType.DAY_INTERVAL_HARVEST;
this.dayFrom = from;
this.dayUntil = until;
}
public XsltTransformerOaiPmhBundlesStreamSource(String oaiPmhEndpoint, Map<String, String> metadataPrefix2XsltMap,
Instant from, Instant until) {
this(oaiPmhEndpoint, metadataPrefix2XsltMap);
this.intervalType = HarvestingIntervalType.SECOND_INTERVAL_HARVEST;
this.secondFrom = from;
this.secondUntil = until;
}
// And here come the same methods but with set specification
public XsltTransformerOaiPmhBundlesStreamSource(String oaiPmhEndpoint, String setSpec,
Map<String, String> metadataPrefix2XsltMap) {
this.oaiPmhEndpoint = oaiPmhEndpoint;
this.setSpec = setSpec;
this.metadataPrefix2XsltMap = metadataPrefix2XsltMap;
this.intervalType = HarvestingIntervalType.FULL_HARVEST;
client = new OaiPmhClient(oaiPmhEndpoint);
}
public XsltTransformerOaiPmhBundlesStreamSource(String oaiPmhEndpoint, String setSpec,
Map<String, String> metadataPrefix2XsltMap, LocalDate from, LocalDate until) {
this(oaiPmhEndpoint, setSpec, metadataPrefix2XsltMap);
this.intervalType = HarvestingIntervalType.DAY_INTERVAL_HARVEST;
this.dayFrom = from;
this.dayUntil = until;
}
public XsltTransformerOaiPmhBundlesStreamSource(String oaiPmhEndpoint, String setSpec,
Map<String, String> metadataPrefix2XsltMap, Instant from, Instant until) {
this(oaiPmhEndpoint, setSpec, metadataPrefix2XsltMap);
this.intervalType = HarvestingIntervalType.SECOND_INTERVAL_HARVEST;
this.secondFrom = from;
this.secondUntil = until;
}
@Override
public Stream<Bundle> getBundlesStream() {
Stream<Bundle> resultBundleStream;
Set<String> metadataPrefixes = metadataPrefix2XsltMap.keySet();
final String from;
final String until;
switch (intervalType) {
case FULL_HARVEST:
from = null;
until = null;
break;
case SECOND_INTERVAL_HARVEST:
from = convertInstantToOaiPmhString(secondFrom);
until = convertInstantToOaiPmhString(secondUntil);
break;
case DAY_INTERVAL_HARVEST:
from = convertLocalDateToOaiPmhString(dayFrom);
until = convertLocalDateToOaiPmhString(dayUntil);
break;
default:
from = null;
until = null;
break;
}
// some record's metadata may not be available for all specified
// metadataPrefixes
// therefore collecting the union of all identifiers over all specified
// metadataPrefixes
Stream<OAIPMHtype> listIdentifiersResponseStream = metadataPrefixes.stream().flatMap(mp -> {
LOG.info("filling list identifier stream with mp {}, from {}, until {}, setSpec {}", mp, from, until,
setSpec);
// exeley only takes "YYY-mm-dd" as from date
String exeleyFrom = from.split("T")[0];
// String exeleyUntil= until.split("T")[0];
LOG.info("exeley specials: {} -> {}", exeleyFrom, until);
if (setSpec == null || setSpec.trim().isEmpty() && mp.equals("pam")) {
return client.listIdentifiersStream(mp, exeleyFrom, until, null);
} else if (setSpec == null || setSpec.trim().isEmpty()) {
return client.listIdentifiersStream(mp, from, until, null);
}
if ( mp.equals("pam") ) {
return client.listIdentifiersStream(mp, exeleyFrom, until, setSpec);
}
return client.listIdentifiersStream(mp, from, until, setSpec);
});
Stream<String> uniqueIdentifiersStream = listIdentifiersResponseStream.flatMap(oaiPmhType -> {
/*
* return oaiPmhType. getListIdentifiers(). getHeader(). stream().map(h ->
* h.getIdentifier() );
*/
Stream<String> result = Stream.empty();
ListIdentifiersType listIdentifiersType = oaiPmhType.getListIdentifiers();
if (null != listIdentifiersType) {
List<HeaderType> headers = listIdentifiersType.getHeader();
if (null != headers) {
result = headers.stream().map(h -> h.getIdentifier());
}
}
return result;
}).filter(Objects::nonNull).distinct();
List<String> uniqueIdentifiers = uniqueIdentifiersStream.collect(Collectors.toList());
// uniqueIdentifiers.forEach(LOG::debug);
resultBundleStream = uniqueIdentifiers.stream().map(this::getBundle).filter(Objects::nonNull);
return resultBundleStream;
}
public Bundle getBundle(String oaiPmhIdentifier) {
LOG.debug("GetBundle - {}", oaiPmhIdentifier);
// Bundle bundleResult = new
// AutonomouslyContentResolvingBundle(ImmutableSet.of() );
Set<Metadatum> bundleMetadata = new HashSet<>();
Set<String> metadataPrefixes = metadataPrefix2XsltMap.keySet();
String lastModifiedString = "";
Bundle resultBundle = null;
// LOG.info("looking up {}", id);
for (String metadataPrefix : metadataPrefixes) {
try {
// --- STEP 1: get XML input
String getRecordXmlDocumentResponseString = client.getRecordString(oaiPmhIdentifier, metadataPrefix);
// LOG.info("--------------------------");
// LOG.info("{}", getRecordXmlDocumentResponseString);
if (isDeletedRecord(getRecordXmlDocumentResponseString)) {
resultBundle = null;
break;
}
else {
// --- STEP 2: convert XML input according to XSLT
TransformerFactory factory = TransformerFactory.newInstance("net.sf.saxon.TransformerFactoryImpl",
TransformerFactoryImpl.class.getClassLoader());
Templates xslTemplate = factory.newTemplates(
new StreamSource(new StringReader(metadataPrefix2XsltMap.get(metadataPrefix))));
Source xmlInput = new StreamSource(new StringReader(getRecordXmlDocumentResponseString));
StringWriter writer = new StringWriter();
Result xmlOutput = new StreamResult(writer);
Transformer transformer = xslTemplate.newTransformer();
transformer.setErrorListener(this);
transformer.transform(xmlInput, xmlOutput);
String xsltConvertedXmlOutput = writer.toString();
// LOG.info("---CONVERTED-----------------------");
// LOG.info("{}", output);
// STEP 3: convert XSLT-converted XML output to Java JAXB object
JAXBContext jaxbContext = JAXBContext.newInstance(XmlBundle.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
InputStream convertedXmlInputStream = new ByteArrayInputStream(
xsltConvertedXmlOutput.getBytes(StandardCharsets.UTF_8));
Bundle currentPartBundle = (Bundle) unmarshaller.unmarshal(convertedXmlInputStream);
Set<Metadatum> metadata = currentPartBundle.getMetadata();
bundleMetadata.addAll(metadata);
// STEP 4: extract lastModifiedDate
StringReader sr = new StringReader(getRecordXmlDocumentResponseString);
// LOG.info("sr=\n{}", getRecordXmlDocumentResponseString);
JAXBContext oaiPmhjaxbContext = JAXBContext.newInstance(OAIPMHtype.class);
Unmarshaller oaiPmhUnmarshaller = oaiPmhjaxbContext.createUnmarshaller();
@SuppressWarnings("unchecked")
JAXBElement<OAIPMHtype> wrappedResponseObject = (JAXBElement<OAIPMHtype>) oaiPmhUnmarshaller
.unmarshal(sr);
OAIPMHtype response = wrappedResponseObject.getValue();
lastModifiedString = response.getGetRecord().getRecord().getHeader().getDatestamp();
// STEP 5: add identifier metadatum
Metadatum reference = new SimpleMetadatum("internal.dda.reference",
oaiPmhEndpoint + "@@" + oaiPmhIdentifier);
bundleMetadata.add(reference);
resultBundle = BundleBuilder.create().withMetadata(bundleMetadata)
.withLastModifiedString(lastModifiedString).build();
}
} catch (XPathException e) {
LOG.debug("Catched XPathException");
String errorCode = e.getErrorCodeLocalPart();
if (null != errorCode) {
if ("filteraway".equals(errorCode)) {
LOG.info("1- filtering away oaiPmhIdentifier={}", oaiPmhIdentifier);
LOG.info("Error detail: {}", e.getCause().getLocalizedMessage());
resultBundle = null;
// test if this break is really necessary, taken out. STJ
// break;
} else {
LOG.warn("2- fatalError. Filtering away oaiPmhIdentifier=" + oaiPmhIdentifier, e);
}
} else {
LOG.warn("3- fatalError. Filtering away oaiPmhIdentifier=" + oaiPmhIdentifier, e);
}
resultBundle = null;
}
catch (Throwable t) {
LOG.warn("Problem getting record with id " + oaiPmhIdentifier + " and metadataPrefix " + metadataPrefix
+ ". Skipping it.", t);
resultBundle = null;
}
}
return resultBundle;
}
public static boolean isDeletedRecord(String getRecordXmlDocumentResponseString) {
XPath xpath = XPathFactory.newInstance().newXPath();
InputSource inputSource = new InputSource(new StringReader(getRecordXmlDocumentResponseString));
String headerStatus;
boolean result;
try {
// see https://stackoverflow.com/a/6397369/923560
XPathExpression expr = xpath
.compile("//*[local-name()='OAI-PMH' and namespace-uri()='http://www.openarchives.org/OAI/2.0/']"
+ "/*[local-name()='GetRecord' and namespace-uri()='http://www.openarchives.org/OAI/2.0/']"
+ "/*[local-name()='record' and namespace-uri()='http://www.openarchives.org/OAI/2.0/']"
+ "/*[local-name()='header' and namespace-uri()='http://www.openarchives.org/OAI/2.0/']/"
+ "@status");
headerStatus = expr.evaluate(inputSource);
if ("deleted".equals(headerStatus)) {
result = true;
} else {
result = false;
}
} catch (XPathExpressionException e) {
LOG.error("Problem identifying if deleted record", e);
result = true;
}
return result;
}
@Override
public String getReference() {
return oaiPmhEndpoint;
}
public String getSetSpec() {
return setSpec;
}
@Override
public Set<Metadatum> getAllMetadata(String scopedIdentifier) {
return getBundle(scopedIdentifier).getMetadata();
}
public static String convertInstantToOaiPmhString(Instant instant) {
String result;
result = instant != null ? instant.toString() : null;
return result;
}
public static String convertLocalDateToOaiPmhString(LocalDate localDate) {
String result;
result = localDate != null ? localDate.toString() : null;
return result;
}
@Override
public void error(TransformerException arg0) throws TransformerException {
LOG.error("error", arg0);
}
@Override
public void fatalError(TransformerException arg0) throws TransformerException {
if (arg0 instanceof XPathException) {
XPathException xPathException = (XPathException) arg0;
String errorCode = xPathException.getErrorCodeLocalPart();
if (null != errorCode) {
if (!"filteraway".equals(errorCode)) {
LOG.error("fatalError", arg0);
}
} else {
LOG.error("fatalError", arg0);
}
} else {
LOG.error("fatalError", arg0);
}
}
@Override
public void warning(TransformerException arg0) throws TransformerException {
LOG.warn("warn", arg0);
}
}
......@@ -238,9 +238,9 @@ public class XsltTransformerOaiPmhBundlesStreamSourceTest {
getClass().
getClassLoader().
getResourceAsStream("xslt/exeley-pam-2-xmlbundle.xslt"), StandardCharsets.UTF_8);
map.put("oai_dc", oaiDcXsltString);
map.put("pam", oaiDcXsltString);
XsltTransformerOaiPmhBundlesStreamSource bss = new XsltTransformerOaiPmhBundlesStreamSource("http://www.exeley.com/oai/", map);
XsltTransformerOaiPmhBundlesStreamSource bss = new XsltTransformerOaiPmhBundlesStreamSource("http://www.exeley.com/oai/OAIRequest", map);