edu.cornell.mannlib.vitro.webapp.controller.individual.IndividualRequestAnalyzer.java Source code

Java tutorial

Introduction

Here is the source code for edu.cornell.mannlib.vitro.webapp.controller.individual.IndividualRequestAnalyzer.java

Source

/* $This file is distributed under the terms of the license in /doc/license.txt$ */

package edu.cornell.mannlib.vitro.webapp.controller.individual;

import static edu.cornell.mannlib.vitro.webapp.controller.VitroHttpServlet.JSON_LD_MIMETYPE;
import static edu.cornell.mannlib.vitro.webapp.controller.VitroHttpServlet.JSON_MIMETYPE;
import static edu.cornell.mannlib.vitro.webapp.controller.VitroHttpServlet.N3_MIMETYPE;
import static edu.cornell.mannlib.vitro.webapp.controller.VitroHttpServlet.RDFXML_MIMETYPE;
import static edu.cornell.mannlib.vitro.webapp.controller.VitroHttpServlet.TTL_MIMETYPE;

import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import edu.cornell.mannlib.vitro.webapp.beans.Individual;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.web.ContentType;

/**
 * All sorts of requests are fielded by the IndividualController. Look at this
 * request and figure out what type it is, and what data we need in order to
 * respond.
 */
public class IndividualRequestAnalyzer {
    private static final Log log = LogFactory.getLog(IndividualRequestAnalyzer.class);

    private static Pattern RDF_REQUEST = Pattern.compile("^/individual/([^/]+)/\\1\\.(rdf|n3|ttl|jsonld)$");
    private static Pattern HTML_REQUEST = Pattern.compile("^/display/([^/]+)$");
    private static Pattern LINKED_DATA_URL = Pattern.compile("^/individual/([^/]+)$");

    private final VitroRequest vreq;
    private final IndividualRequestAnalysisContext analysisContext;
    private final String url;

    public IndividualRequestAnalyzer(VitroRequest vreq, IndividualRequestAnalysisContext analysisContext) {
        this.vreq = vreq;

        // get URL without hostname or servlet context
        this.url = vreq.getRequestURI().substring(vreq.getContextPath().length());

        this.analysisContext = analysisContext;
    }

    public IndividualRequestInfo analyze() {
        // If this is a request for RDF using an accept header, redirect it to
        // the preferred URL.
        String redirectUrl = checkForRedirect();
        if (redirectUrl != null) {
            return IndividualRequestInfo.buildRdfRedirectInfo(redirectUrl);
        }

        // Figure out what individual we are talking about. If we can't figure
        // it out, complain.
        Individual individual = getIndividualFromRequest();
        if (individual == null) {
            return IndividualRequestInfo.buildNoIndividualInfo();
        }

        // If the requested individual is a FileBytestream, redirect to its
        // "alias URL".
        redirectUrl = getAliasUrlForBytestreamIndividual(individual);
        if (redirectUrl != null) {
            return IndividualRequestInfo.buildBytestreamRedirectInfo(redirectUrl);
        }

        // Check to see whether Linked Data was requested.
        ContentType rdfFormat = checkUrlForLinkedDataRequest();
        if (rdfFormat != null) {
            return IndividualRequestInfo.buildLinkedDataInfo(individual, rdfFormat);
        }

        // No redirect, no Linked Data; no problem.
        return IndividualRequestInfo.buildDefaultInfo(individual);
    }

    /*
     * Following recipe 3 from
     * "Best Practice Recipes for Publishing RDF Vocabularies." See
     * http://www.w3.org/TR/swbp-vocab-pub/#recipe3. The basic idea is that a
     * URI like http://vivo.cornell.edu/individual/n1234 identifies a real world
     * individual. HTTP cannot send that as the response to a GET request
     * because it can only send bytes and not things. The server sends a 303, to
     * mean "you asked for something I cannot send you, but I can send you this
     * other stream of bytes about that thing." In the case of a request like
     * http://vivo.cornell.edu/individual/n1234/n1234.rdf or
     * http://vivo.cornell.edu/individual/n1234?format=rdfxml, the request is
     * for a set of bytes rather than an individual, so no 303 is needed.
     */
    private static Pattern URI_PATTERN = Pattern.compile("^/individual/([^/]*)$");

    private String checkForRedirect() {
        // A "format" parameter is special, and is dealt with elsewhere.
        String formatParam = getRequestParameter("format", "");
        if (!formatParam.isEmpty()) {
            return null;
        }

        // Is it "/individual/" followed by a single group?
        Matcher m = URI_PATTERN.matcher(url);
        if (!m.matches() || m.groupCount() < 1) {
            return null;
        }

        // Then, use the "accept" header to decide how to redirect it.
        ContentType c = checkAcceptHeaderForLinkedDataRequest();
        if (c != null) {
            String mediaType = c.getMediaType();
            if (RDFXML_MIMETYPE.equals(mediaType)) {
                return "/individual/" + m.group(1) + "/" + m.group(1) + ".rdf";
            } else if (N3_MIMETYPE.equals(mediaType)) {
                return "/individual/" + m.group(1) + "/" + m.group(1) + ".n3";
            } else if (TTL_MIMETYPE.equals(mediaType)) {
                return "/individual/" + m.group(1) + "/" + m.group(1) + ".ttl";
            } else if (JSON_MIMETYPE.equals(mediaType) || JSON_LD_MIMETYPE.equals(mediaType)) {
                return "/individual/" + m.group(1) + "/" + m.group(1) + ".jsonld";
            }
        }
        // or redirect to the canonical URL for HTML representation.
        return "/display/" + m.group(1);
    }

    /**
     * Check the accept header. This request will trigger a redirect with a 303
     * ("see also"), because the request is for an individual but the server can
     * only provide a set of bytes.
     */
    protected ContentType checkAcceptHeaderForLinkedDataRequest() {
        String acceptHeader = vreq.getHeader("Accept");
        if (acceptHeader == null)
            acceptHeader = vreq.getHeader("accept");
        if (acceptHeader == null)
            return null;

        try {
            Map<String, Float> typesAndQ = ContentType.getTypesAndQ(acceptHeader);
            String ctStr = ContentType.getBestContentType(typesAndQ, IndividualController.ACCEPTED_CONTENT_TYPES);

            if (RDFXML_MIMETYPE.equals(ctStr) || N3_MIMETYPE.equals(ctStr) || TTL_MIMETYPE.equals(ctStr)
                    || JSON_MIMETYPE.equals(ctStr) || JSON_LD_MIMETYPE.equals(ctStr)) {
                return new ContentType(ctStr);
            }
        } catch (Throwable th) {
            log.error("Problem while checking accept header ", th);
        }
        return null;
    }

    /**
     * Gets the entity id from the request. Works for the following styles of
     * URLs:
     * 
     * <pre>
     *     /individual?uri=urlencodedURI
     *     /individual?netId=bdc34
     *     /individual?netid=bdc34
     *     /individual/localname         
     *     /display/localname
     *     /individual/localname/localname.rdf
     *     /individual/localname/localname.n3
     *     /individual/localname/localname.ttl
     *     /individual/localname/localname.jsonld
     * </pre>
     * 
     * @return null on failure.
     */
    public Individual getIndividualFromRequest() {
        try {
            // Check for "uri" parameter.
            String uri = getRequestParameter("uri", "");
            if (!uri.isEmpty()) {
                return getIndividualByUri(uri);
            }

            // Check for "netId" or "netid" parameter
            String netId = getRequestParameter("netId", getRequestParameter("netid", ""));
            if (!netId.isEmpty()) {
                return getIndividualByNetId(netId);
            }

            // Check for just a local name
            Matcher linkedDataMatch = LINKED_DATA_URL.matcher(url);
            if (linkedDataMatch.matches() && linkedDataMatch.groupCount() == 1) {
                return getIndividualByLocalname(linkedDataMatch.group(1));
            }

            // Check for the canonical HTML request.
            Matcher htmlMatch = HTML_REQUEST.matcher(url);
            if (htmlMatch.matches() && htmlMatch.groupCount() == 1) {
                return getIndividualByLocalname(htmlMatch.group(1));
            }

            // Check for a request for RDF.
            Matcher rdfMatch = RDF_REQUEST.matcher(url);
            if (rdfMatch.matches() && rdfMatch.groupCount() == 2) {
                return getIndividualByLocalname(rdfMatch.group(1));
            }

            // Couldn't match it to anything.
            return null;
        } catch (Throwable e) {
            log.error("Problems trying to find Individual", e);
            return null;
        }
    }

    private String getAliasUrlForBytestreamIndividual(Individual individual) {
        String aliasUrl = analysisContext.getAliasUrlForBytestreamIndividual(individual);

        if (individual.getURI().equals(aliasUrl)) {
            // Avoid a tight loop; if the alias URL is equal to the URI,
            // then don't recognize it as a FileBytestream.
            return null;
        } else {
            return aliasUrl;
        }
    }

    /**
     * @return null if this is not a linked data request, returns content type
     *         if it is a linked data request.
     * 
     *         These are Vitro-specific ways of requesting rdf, unrelated to
     *         semantic web standards. They do not trigger a redirect with a
     *         303, because the request is for a set of bytes rather than an
     *         individual.
     */
    protected ContentType checkUrlForLinkedDataRequest() {
        /*
         * Check for url param specifying format. Example:
         * http://vivo.cornell.edu/individual/n23?format=rdfxml
         */
        String formatParam = getRequestParameter("format", "");
        if (formatParam.contains("rdfxml")) {
            return ContentType.RDFXML;
        }
        if (formatParam.contains("n3")) {
            return ContentType.N3;
        }
        if (formatParam.contains("ttl")) {
            return ContentType.TURTLE;
        }
        if (formatParam.contains("jsonld") || formatParam.contains("json")) {
            return ContentType.JSON;
        }

        /*
         * Check for parts of URL that indicate request for RDF. Examples:
         * http://vivo.cornell.edu/individual/n23/n23.rdf
         * http://vivo.cornell.edu/individual/n23/n23.n3
         * http://vivo.cornell.edu/individual/n23/n23.ttl
         * http://vivo.cornell.edu/individual/n23/n23.jsonld
         */
        Matcher rdfMatch = RDF_REQUEST.matcher(url);
        if (rdfMatch.matches() && rdfMatch.groupCount() == 2) {
            String rdfType = rdfMatch.group(2);
            if ("rdf".equals(rdfType)) {
                return ContentType.RDFXML;
            }
            if ("n3".equals(rdfType)) {
                return ContentType.N3;
            }
            if ("ttl".equals(rdfType)) {
                return ContentType.TURTLE;
            }
            if ("jsonld".equals(rdfType)) {
                return ContentType.JSON;
            }
        }

        return null;
    }

    private String getRequestParameter(String key, String defaultValue) {
        String value = vreq.getParameter(key);
        if ((value == null) || value.isEmpty()) {
            return defaultValue;
        } else {
            return value;
        }
    }

    private Individual getIndividualByUri(String uri) {
        return analysisContext.getIndividualByURI(uri);
    }

    private Individual getIndividualByLocalname(String localname) {
        String defaultNamespace = analysisContext.getDefaultNamespace();
        String uri = defaultNamespace + localname;
        return getIndividualByUri(uri);
    }

    private Individual getIndividualByNetId(String netId) {
        return analysisContext.getIndividualByNetId(netId);
    }
}