All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.dspace.rdf.negotiation.Negotiator Maven / Gradle / Ivy

There is a newer version: 8.0
Show newest version
/**
 * The contents of this file are subject to the license and copyright
 * detailed in the LICENSE and NOTICE files at the root of the source
 * tree and available online at
 *
 * http://www.dspace.org/license/
 */
package org.dspace.rdf.negotiation;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.validator.routines.UrlValidator;
import org.apache.logging.log4j.Logger;
import org.dspace.rdf.RDFUtil;
import org.dspace.services.factory.DSpaceServicesFactory;

/**
 * @author Pascal-Nicolas Becker (dspace -at- pascal -hyphen- becker -dot- de)
 */
public class Negotiator {

    // Serialiazation codes
    public static final int UNSPECIFIED = -1;
    public static final int WILDCARD = 0;
    public static final int HTML = 1;
    public static final int RDFXML = 2;
    public static final int TURTLE = 3;
    public static final int N3 = 4;

    public static final String DEFAULT_LANG = "html";

    private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(Negotiator.class);

    /**
     * Default constructor
     */
    private Negotiator() { }

    public static int negotiate(String acceptHeader) {
        if (acceptHeader == null) {
            return UNSPECIFIED;
        }

        String[] mediaRangeSpecs = acceptHeader.split(",");
        ArrayList requestedMediaRanges = new ArrayList<>();
        for (String mediaRangeSpec : mediaRangeSpecs) {
            try {
                requestedMediaRanges.add(new MediaRange(mediaRangeSpec));
            } catch (IllegalArgumentException | IllegalStateException ex) {
                log.warn("Couldn't parse part of an AcceptHeader, ignoring it.\n"
                             + ex.getMessage(), ex);
            }
        }
        if (requestedMediaRanges.isEmpty()) {
            return UNSPECIFIED;
        }

        Collections.sort(requestedMediaRanges, getMediaRangeComparator());
        Collections.reverse(requestedMediaRanges);

        if (log.isDebugEnabled()) {
            StringBuilder sb = new StringBuilder("Parsed Accept header '" + acceptHeader + "':\n");
            for (Iterator it = requestedMediaRanges.iterator(); it.hasNext(); ) {
                MediaRange mr = it.next();
                sb.append(mr.getType()).append("/").append(mr.getSubtype());
                sb.append(" has a qvalue of ").append(Double.toString(mr.getQvalue()));
                sb.append("\n");
            }
            log.debug(sb.toString());
        }

        boolean wildcard = false;
        boolean html = false;
        boolean rdf = false;
        boolean n3 = false;
        boolean turtle = false;
        Iterator it = requestedMediaRanges.iterator();

        MediaRange lookahead = it.hasNext() ? it.next() : null;
        while (lookahead != null) {
            double qvalue = lookahead.getQvalue();
            String type = lookahead.getType();
            String subtype = lookahead.getSubtype();

            lookahead = it.hasNext() ? it.next() : null;

            if (qvalue <= 0.0) {
                // a quality of 0.0 means that the defined media range should
                // not to be send => don't parse it.
                continue;
            }

            if ("*".equals(type)) {
                wildcard = true;
            }
            if (("text".equals(type) && "html".equals(subtype))
                || ("application".equals(type) && "xhtml+xml".equals(subtype))) {
                html = true;
            }
            if ("application".equals(type) && "rdf+xml".equals(subtype)) {
                rdf = true;
            }
            if (("text".equals(type) && "n3".equals(subtype))
                || ("text".equals(type) && "rdf+n3".equals(subtype))
                || ("application".equals(type) && "n3".equals(subtype))) {
                n3 = true;
            }
            if (("text".equals(type) && "turtle".equals(subtype))
                || ("application".equals(type) && "turtle".equals(subtype))
                || ("application".equals(type) && "x-turtle".equals(subtype))
                || ("application".equals(type) && "rdf+turtle".equals(subtype))) {
                turtle = true;
            }

            if (lookahead != null
                && qvalue != lookahead.qvalue
                && (wildcard || html || rdf || n3 || turtle)) {
                // we've looked over all media range with the same precedence
                // and found one, we can serve
                break;
            }
        }

        if (html) {
            return HTML;
        }
        if (wildcard) {
            return WILDCARD;
        } else if (turtle) {
            return TURTLE;
        } else if (n3) {
            return N3;
        } else if (rdf) {
            return RDFXML;
        }

        return UNSPECIFIED;
    }

    /**
     * Method to get a comparator to compare media ranges regarding their
     * content negotiation precedence. Following RFC 2616 a media range is
     * higher prioritized then another media range if the first one has a higher
     * quality value then the second. If both quality values are equal, the
     * media range that is more specific should be used.
     *
     * 

Note: this comparator imposes orderings that are inconsistent with * equals! Caution should be exercised when using it to order a sorted set * or a sorted map. Take a look at the java.util.Comparator for further * information.

* * @return A comparator that imposes orderings that are inconsistent with equals! */ public static Comparator getMediaRangeComparator() { return new Comparator() { @Override public int compare(MediaRange mr1, MediaRange mr2) { if (Double.compare(mr1.qvalue, mr2.getQvalue()) != 0) { return Double.compare(mr1.qvalue, mr2.getQvalue()); } if (mr1.typeIsWildcard() && mr2.typeIsWildcard()) { return 0; } if (mr1.typeIsWildcard() && !mr2.typeIsWildcard()) { return -1; } if (!mr1.typeIsWildcard() && mr2.typeIsWildcard()) { return 1; } if (mr1.subtypeIsWildcard() && mr2.subtypeIsWildcard()) { return 0; } if (mr1.subtypeIsWildcard() && !mr2.subtypeIsWildcard()) { return -1; } if (!mr1.subtypeIsWildcard() && mr2.subtypeIsWildcard()) { return 1; } // if the quality of two media ranges is equal and both don't // use an asterisk either as type or subtype, they are equal in // the sense of content negotiation precedence. return 0; } }; } public static boolean sendRedirect(HttpServletResponse response, String handle, String extraPathInfo, int serialization, boolean redirectHTML) throws IOException { if (extraPathInfo == null) { extraPathInfo = ""; } UrlValidator urlValidator = new UrlValidator(UrlValidator.ALLOW_LOCAL_URLS); StringBuilder urlBuilder = new StringBuilder(); String lang = null; switch (serialization) { case (Negotiator.UNSPECIFIED): case (Negotiator.WILDCARD): { lang = DEFAULT_LANG; break; } case (Negotiator.HTML): { lang = "html"; break; } case (Negotiator.RDFXML): { lang = "rdf"; break; } case (Negotiator.TURTLE): { lang = "turtle"; break; } case (Negotiator.N3): { lang = "n3"; break; } default: { lang = DEFAULT_LANG; break; } } assert (lang != null); if (StringUtils.isEmpty(handle)) { log.warn("Handle is empty, set it to Site Handle."); handle = DSpaceServicesFactory.getInstance().getConfigurationService() .getProperty("handle.prefix") + "/0"; } // don't redirect if HTML is requested and content negotiation is done // in a ServletFilter, as the ServletFilter should just let the request // pass. if ("html".equals(lang) && !redirectHTML) { return false; } // as we do content negotiation and we'll redirect the request, we // should send a vary caching so browsers can adopt their caching strategy response.setHeader("Vary", "Accept"); // if html is requested we have to forward to the repositories webui. if ("html".equals(lang)) { urlBuilder.append(DSpaceServicesFactory.getInstance() .getConfigurationService().getProperty("dspace.ui.url")); if (!handle.equals(DSpaceServicesFactory.getInstance() .getConfigurationService().getProperty("handle.prefix") + "/0")) { urlBuilder.append("/handle/"); urlBuilder.append(handle).append("/").append(extraPathInfo); } String url = urlBuilder.toString(); if (urlValidator.isValid(url)) { log.debug("Will forward to '" + url + "'."); response.setStatus(HttpServletResponse.SC_SEE_OTHER); response.setHeader("Location", url); response.flushBuffer(); return true; } else { throw new IOException("Invalid URL '" + url + "', cannot redirect."); } } // currently we cannot serve statistics as rdf if ("statistics".equals(extraPathInfo)) { log.info("Cannot send statistics as RDF yet. => 406 Not Acceptable."); response.sendError(HttpServletResponse.SC_NOT_ACCEPTABLE); response.flushBuffer(); return true; } // load the URI of the dspace-rdf module. urlBuilder.append(DSpaceServicesFactory.getInstance() .getConfigurationService() .getProperty(RDFUtil.CONTEXT_PATH_KEY)); if (urlBuilder.length() == 0) { log.error("Cannot load URL of dspace-rdf module. " + "=> 500 Internal Server Error"); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); response.flushBuffer(); return true; } // and build the uri to the DataProviderServlet urlBuilder.append("/handle/").append(handle); urlBuilder.append("/").append(lang); String url = urlBuilder.toString(); if (urlValidator.isValid(url)) { log.debug("Will forward to '" + url + "'."); response.setStatus(HttpServletResponse.SC_SEE_OTHER); response.setHeader("Location", url); response.flushBuffer(); return true; } else { throw new IOException("Invalid URL '" + url + "', cannot redirect."); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy