/*
* Copyright (C) 2015 Jan Pokorsky
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cz.cas.lib.proarc.common.catalog;
import cz.cas.lib.proarc.common.config.CatalogConfiguration;
import cz.cas.lib.proarc.common.mods.ModsUtils;
import cz.cas.lib.proarc.common.xml.Transformers;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.WebTarget;
import javax.xml.transform.ErrorListener;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamSource;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature;
import org.glassfish.jersey.logging.LoggingFeature;
/**
* The catalog can query OAI repositories with
* <a href='http://www.openarchives.org/OAI/openarchivesprotocol.html#GetRecord'>GetRecord</a> queries.
*
* @author Jan Pokorsky
*/
public class OaiCatalog implements BibliographicCatalog {
public static final String TYPE = "OAICatalog";
/**
* The field name used to query OAI repository.
*/
static final String FIELD_ID = "id";
/**
* The optional prefix for the identifier in GetRecord queries.
*/
static final String PROPERTY_IDENTIFIER_PREFIX = "identifierPrefix";
/**
* The metadata prefix for GetRecord queries.
*/
static final String PROPERTY_METADATA_PREFIX = "metadataPrefix";
private static Templates OAI_MARC_XSLT;
private static final Logger LOG = Logger.getLogger(OaiCatalog.class.getName());
private Client httpClient;
private final String url;
private String user;
private String password;
private final String metadataPrefix;
private String identifierPrefix;
private final Transformers transformers;
public static OaiCatalog get(CatalogConfiguration c) {
if (c == null || !TYPE.equals(c.getType())) {
return null;
}
String url = c.getUrl();
String metadataPrefix = c.getProperty(PROPERTY_METADATA_PREFIX);
OaiCatalog cat = new OaiCatalog(url, metadataPrefix);
cat.setIdentifierPrefix(c.getProperty(PROPERTY_IDENTIFIER_PREFIX, null));
cat.setUser(c.getProperty(CatalogConfiguration.PROPERTY_USER, null));
cat.setPassword(c.getProperty(CatalogConfiguration.PROPERTY_PASSWD, null));
cat.setDebug(c.getDebug());
return cat;
}
public OaiCatalog(String url, String metadataPrefix) {
this(url, metadataPrefix, null);
}
public OaiCatalog(String url, String metadataPrefix, String identifierPrefix) {
this.url = url;
this.metadataPrefix = metadataPrefix;
this.identifierPrefix = identifierPrefix;
this.transformers = new Transformers();
}
public void setDebug(boolean debug) {
LOG.setLevel(debug ? Level.FINEST : null);
}
public void setIdentifierPrefix(String identifierPrefix) {
this.identifierPrefix = identifierPrefix;
}
public void setUser(String user) {
this.user = user;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public List<MetadataItem> find(String fieldName, String value, Locale locale) throws TransformerException, IOException {
WebTarget query = buildOaiQuery(fieldName, value);
String oaiResponse = findOaiRecord(query);
ArrayList<MetadataItem> result = new ArrayList<MetadataItem>();
if (oaiResponse != null) {
DOMResult marcResult = transformOaiResponse(
new StreamSource(new StringReader(oaiResponse)), new DOMResult());
if (marcResult != null) {
MetadataItem item = createResponse(0, new DOMSource(marcResult.getNode()), locale);
result.add(item);
}
}
return result;
}
public String findOaiRecord(String id) {
return findOaiRecord(buildOaiQuery(id));
}
String findOaiRecord(WebTarget query) {
if (query == null) {
return null;
}
String result = query.request().get(String.class);
return result;
}
WebTarget buildOaiQuery(String fieldName, String value) {
WebTarget query = null;
if (FIELD_ID.equals(fieldName)) {
query = buildOaiQuery(value);
}
return query;
}
WebTarget buildOaiQuery(String id) {
if (identifierPrefix != null && !id.startsWith(identifierPrefix)) {
id = identifierPrefix + id;
}
return getClient().target(url)
.queryParam("verb", "GetRecord")
.queryParam("identifier", id)
.queryParam("metadataPrefix", metadataPrefix);
}
private Client getClient() {
if (httpClient == null) {
httpClient = createClient();
}
return httpClient;
}
private Client createClient() {
ClientBuilder builder = ClientBuilder.newBuilder();
if (user != null) {
builder.register(HttpAuthenticationFeature.basic(user, password));
}
if (LOG.isLoggable(Level.FINEST)) {
builder.register(new LoggingFeature(LOG));
}
Client client = builder
.property(ClientProperties.FOLLOW_REDIRECTS, true)
.property(ClientProperties.CONNECT_TIMEOUT, 2 * 60 * 1000) // 2 min
.property(ClientProperties.READ_TIMEOUT, 1 * 60 * 1000) // 1 min
.build();
return client;
}
private MetadataItem createResponse(int entryIdx, Source marcxmlSrc, Locale locale)
throws TransformerException, UnsupportedEncodingException {
byte[] modsBytes = transformers.transformAsBytes(
marcxmlSrc, Transformers.Format.MarcxmlAsMods3);
byte[] modsHtmlBytes = modsAsHtmlBytes(new StreamSource(new ByteArrayInputStream(modsBytes)), locale);
byte[] modsTitleBytes = transformers.transformAsBytes(
new StreamSource(new ByteArrayInputStream(modsBytes)),
Transformers.Format.ModsAsTitle);
return new MetadataItem(entryIdx, new String(modsBytes, "UTF-8"),
new String(modsHtmlBytes, "UTF-8"), new String(modsTitleBytes, "UTF-8"));
}
private byte[] modsAsHtmlBytes(Source source, Locale locale) throws TransformerException {
byte[] modsHtmlBytes = transformers.transformAsBytes(
source, Transformers.Format.ModsAsHtml, ModsUtils.modsAsHtmlParameters(locale));
return modsHtmlBytes;
}
/**
* @return the result metadata or {@code null} for empty result.
*/
<T extends Result> T transformOaiResponse(Source src, T dst) throws TransformerException {
Transformer t = getOai2MarcXslt().newTransformer();
XslErrorListener errorListener = new XslErrorListener();
t.setErrorListener(errorListener);
try {
t.transform(src, dst);
return dst;
} catch (TransformerException ex) {
// ignore ID not found
if (errorListener.containError(XslErrorListener.ERR_ID_DOESNOT_EXIST)) {
return null;
} else if (!errorListener.getMessages().isEmpty()) {
throw new TransformerException(errorListener.getMessages().toString(), ex);
}
throw ex;
}
}
Templates getOai2MarcXslt() throws TransformerConfigurationException {
return createOai2MarcXslt();
}
private static Templates createOai2MarcXslt() throws TransformerConfigurationException {
if (OAI_MARC_XSLT == null) {
String xsltSrc = OaiCatalog.class.getResource("/xml/Oai2MARC21slim.xsl").toExternalForm();
OAI_MARC_XSLT = TransformerFactory.newInstance().newTemplates(new StreamSource(xsltSrc));
}
return OAI_MARC_XSLT;
}
private static final class XslErrorListener implements ErrorListener {
static final String ERR_INVALID_METADATA_FORMAT = "Invalid metadata format:";
static final String ERR_ID_DOESNOT_EXIST = "idDoesNotExist:";
private final List<String> messages = new ArrayList<String>();
public List<String> getMessages() {
return messages;
}
public boolean containError(String err) {
for (String message : messages) {
if (message.startsWith(err)) {
return true;
}
}
return false;
}
@Override
public void warning(TransformerException exception) throws TransformerException {
messages.add(exception.getMessage().trim());
}
@Override
public void error(TransformerException exception) throws TransformerException {
messages.add(exception.getMessage().trim());
}
@Override
public void fatalError(TransformerException exception) throws TransformerException {
throw exception;
}
}
}