unidue.ub.statistics.resolver.JOPResolver.java Source code

Java tutorial

Introduction

Here is the source code for unidue.ub.statistics.resolver.JOPResolver.java

Source

package unidue.ub.statistics.resolver;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import javax.xml.transform.URIResolver;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.log4j.Logger;
import org.jdom2.Attribute;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.filter.ElementFilter;
import org.jdom2.filter.Filters;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;
import org.jdom2.transform.JDOMSource;
import org.jdom2.xpath.XPathExpression;
import org.jdom2.xpath.XPathFactory;
import org.mycore.common.MCRCache;
import org.mycore.common.config.MCRConfiguration;
import org.mycore.common.MCRSessionMgr;
import org.mycore.common.content.MCRByteContent;
import org.xml.sax.SAXException;

/**
 * Queries the "Journals Online & Print" service to
 * retrieve information on online and print availability
 * of journals. Returns the "full xml" format from JOP.
 * 
 * http://www.zeitschriftendatenbank.de/services/journals-online-print
 * 
 * Usage: jop:{queryString} where queryString is same as in any web request to JOP
 * Usage: jop:session to get the last JOP response that was queried in the current user's session
 * 
 * @author Frank L\u00FCtzenkirchen
 */
public class JOPResolver implements URIResolver {

    private final static Logger LOGGER = Logger.getLogger(JOPResolver.class);

    /** A key to store JOP information in the current user's session */
    private final static String JOPKEY = "JOP";

    /** URL of the JOP full xml service, set via property JOP.FullXML.URL */
    private final static String jopFullURL;

    /** URL of the JOP brief xml service, set via property JOP.BriefXML.URL */
    private final static String jopBriefURL;

    /** Timeout (millis) for requests to JOP, set via property JOP.TimeOut (seconds) */
    private final static int timeout;

    /** Maximum age of JOP requests (millis) in cache, set via property JOP.Cache.MaxAge (seconds) */
    private final static int cacheMaxAge;

    private final static int cacheSize;

    /** A cache of JOP requests, where key is the query string */
    private MCRCache<String, Element> cache;

    static {
        MCRConfiguration config = MCRConfiguration.instance();
        jopFullURL = config.getString("JOP.FullXML.URL");
        jopBriefURL = config.getString("JOP.BriefXML.URL");
        timeout = config.getInt("JOP.TimeOut", 10) * 1000;
        cacheMaxAge = config.getInt("JOP.Cache.MaxAge", 10) * 1000;
        cacheSize = config.getInt("JOP.Cache.Size", 50);
    }

    public JOPResolver() {
        cache = new MCRCache<String, Element>(cacheSize, JOPKEY);
    }

    @Override
    public Source resolve(String href, String base) throws TransformerException {
        String query = href.substring(4);
        LOGGER.info(query);

        try {
            Element root = "session".equals(query) ? getJOPDataFromSession() : getJOPDataFromCache(query);
            if (root == null) {
                root = getJOPDataFromRemoteServer(query, jopFullURL);
                fixAtInTitles(root);
                root.setAttribute("query", query);
                addDataFromOpenURLQuery(root, query);

                // if (isElectronicDataState3(root))
                //    addElectronicDataTotalState(root, query);

                cache.put(query, root);
            }
            MCRSessionMgr.getCurrentSession().put(JOPKEY, root);
            return new JDOMSource(root);
        } catch (Exception ex) {
            throw new TransformerException(ex);
        }
    }

    /** The xpath under which the JOP brief response contains the total state of electronic availability */
    private final static String xpathToElectronicDataState = "/OpenURLResponseXML/Brief/ElectronicData/@state";

    /** The xpath under which the JOP full response contains the information on electronic availability */
    private final static String xpathToElectronicDataFull = "/OpenURLResponseXML/Full/ElectronicData";

    /** The additional xpath to check if any result has state '3' (partially licensed) */
    private final static String xpathPredicateState3 = "/ResultList[Result/@state='3']";

    /**
     * Returns true, if any electronic data is marked with state '3' (partially licensed) in the given
     * full XML response of JOP. 
     */
    private boolean isElectronicDataState3(Element fullXML) throws JDOMException {
        String path = xpathToElectronicDataFull + xpathPredicateState3;
        XPathExpression<Element> xPath = XPathFactory.instance().compile(path, Filters.element());
        boolean result = (null != xPath.evaluateFirst(fullXML));
        LOGGER.debug("electronic availability is marked as partially licensed? " + result);
        return result;
    }

    /**
     * Queries the JOP brief xml service to get the total status of electronic availability, and adds that state
     * to the ElectronicData element. In case total access is given by combining multiple licenses with state '3' 
     * (partially licensed), only the brief response contains the information that the combined license covers
     * the complete journal, if so. 
     */
    private void addElectronicDataTotalState(Element fullXML, String query)
            throws SAXException, IOException, JDOMException {
        Element briefData = getJOPDataFromRemoteServer(query, jopBriefURL);
        XPathExpression<Attribute> xPath = XPathFactory.instance().compile(xpathToElectronicDataState,
                Filters.attribute());
        Attribute state = xPath.evaluateFirst(briefData);
        LOGGER.debug("electronic availability total state from brief xml response is " + state.getValue());
        Element electronicData = XPathFactory.instance().compile(xpathToElectronicDataFull, Filters.element())
                .evaluateFirst(fullXML);
        state.detach();
        electronicData.setAttribute(state);
    }

    /** Remove all @ contained in "Title" elements that seem to separate to non-sort part of the title from the rest */
    private void fixAtInTitles(Element root) {
        List<Element> candidates = new ArrayList<Element>();

        Iterator<Element> titles = root.getDescendants(new ElementFilter("Title"));
        while (titles.hasNext()) {
            Element title = titles.next();
            String text = title.getText();
            if ((text != null) && text.contains("@"))
                candidates.add(title);
        }

        for (Element title : candidates)
            title.setText(title.getText().replaceAll("\\s@(\\S)", " $1"));
    }

    /**
     * Queries the Journals Online & Print service to retrieve the XML data
     * on journal availability.
     */
    private Element getJOPDataFromRemoteServer(String query, String baseURL)
            throws IOException, SAXException, JDOMException {
        String url = baseURL + "?" + query;
        GetMethod getMethod = new GetMethod(url);
        getMethod.setFollowRedirects(true);
        HttpClient client = new HttpClient();
        client.getParams().setConnectionManagerTimeout(timeout);
        client.getParams().setSoTimeout(timeout);

        int responseCode = client.executeMethod(getMethod);
        if (responseCode != 200) {
            LOGGER.error("JOP returned response code " + responseCode);
            throw new JOPException(getMethod);
        }

        byte[] response = getMethod.getResponseBody();
        getMethod.releaseConnection();

        Element jopData = new MCRByteContent(response).asXML().getRootElement();
        debugJOPData(jopData);
        return jopData;
    }

    /** 
     * A JOPException is thrown when requests to JOP return with HTTP errors.
     */
    public class JOPException extends RuntimeException {

        /**
        * 
        */
        private static final long serialVersionUID = 1L;
        /** The HTTP error code returned from the JOP request */
        private int errorCode;

        public JOPException(GetMethod getMethod) throws IOException {
            super(getResponseError(getMethod));
            this.errorCode = getMethod.getStatusCode();
        }

        /** Returns the HTTP error code returned from the JOP request 
         * @return the error Code
         * */
        public int getErrorCode() {
            return errorCode;
        }

    }

    private static String getResponseError(GetMethod getMethod) throws IOException {
        String msg = "Der JOP-Dienst von EZB/ZDB meldet einen Fehler:";
        String uri = getMethod.getURI().toString();
        String text = getMethod.getStatusText();
        String body = getMethod.getResponseBodyAsString();
        if (body == null)
            body = "";
        if (body.contains("<body>"))
            body = body.substring(body.indexOf("<body>") + 6);
        if (body.contains("</body>"))
            body = body.substring(0, body.indexOf("</body>"));
        return msg + "\n\n" + uri + "\n\n" + text + "\n\n" + body;
    }

    /**
     * Returns the JOP data that was last used in the current user's session (URI: jop:session)
     */
    private Element getJOPDataFromSession() throws TransformerException {
        Element jopData = (Element) (MCRSessionMgr.getCurrentSession().get(JOPKEY));
        if (jopData == null) {
            throw new TransformerException("No JOP data in session");
        }
        return jopData;
    }

    /**
     * Returns the JOP data that was cached for the given request query.
     * 
     * @return the JOP data from cache, if any, and only of it is not older than the maximum cache age.
     */
    private Element getJOPDataFromCache(String query) {
        long maxAge = System.currentTimeMillis() - cacheMaxAge;
        Element jopData = (Element) (cache.getIfUpToDate(query, maxAge));
        if (jopData != null)
            LOGGER.debug("JOP data retrieved from cache");
        return jopData;
    }

    private void debugJOPData(Element jopData) {
        if (!LOGGER.isDebugEnabled())
            return;

        XMLOutputter xout = new XMLOutputter(Format.getPrettyFormat());
        String output = xout.outputString(jopData);
        LOGGER.debug("\n\n" + output + "\n\n");
    }

    /**
     * Parses the OpenURL request and adds the query parameters as XML element 
     * for later use in the order form. 
     *  
     * @param parent
     * @param query
     */
    private void addDataFromOpenURLQuery(Element parent, String query) {
        if (query == null)
            return;

        Element q = new Element("query");
        for (String pair : query.split("&")) {
            int pos = pair.indexOf("=");
            if ((pos == -1) || (pos == pair.length() - 1))
                continue;
            try {
                String name = pair.substring(0, pos);
                String value = URLDecoder.decode(pair.substring(pos + 1), "UTF-8");
                Element e = new Element(name).setText(value);
                q.addContent(e);
            } catch (UnsupportedEncodingException ignored) {
            }
        }

        if (!q.getChildren().isEmpty())
            parent.addContent(q);
    }
}