/* * 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; } } }