org.mycore.restapi.v1.MCRRestAPIClassifications.java Source code

Java tutorial

Introduction

Here is the source code for org.mycore.restapi.v1.MCRRestAPIClassifications.java

Source

/*
 * $Revision: 29635 $ $Date: 2014-04-10 10:55:06 +0200 (Do, 10 Apr 2014) $
 *
 * This file is part of ***  M y C o R e  ***
 * See http://www.mycore.de/ for details.
 *
 * This program is free software; you can use it, redistribute it
 * and / or modify it under the terms of the GNU General Public License
 * (GPL) as published by the Free Software Foundation; either version 2
 * 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, in a file called gpl.txt or license.txt.
 * If not, write to the Free Software Foundation Inc.,
 * 59 Temple Place - Suite 330, Boston, MA  02111-1307 USA
 */

package org.mycore.restapi.v1;

import java.io.IOException;
import java.io.StringWriter;

import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrDocumentList;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.Namespace;
import org.jdom2.filter.Filters;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;
import org.jdom2.xpath.XPathExpression;
import org.jdom2.xpath.XPathFactory;
import org.mycore.datamodel.classifications2.MCRCategory;
import org.mycore.datamodel.classifications2.MCRCategoryDAO;
import org.mycore.datamodel.classifications2.MCRCategoryID;
import org.mycore.datamodel.classifications2.impl.MCRCategoryDAOImpl;
import org.mycore.datamodel.classifications2.utils.MCRCategoryTransformer;
import org.mycore.frontend.jersey.MCRStaticContent;
import org.mycore.restapi.v1.errors.MCRRestAPIError;
import org.mycore.solr.MCRSolrClientFactory;
import org.mycore.solr.MCRSolrUtils;

import com.google.gson.stream.JsonWriter;

/**
 * REST API for classification objects.
 *
 * @author Robert Stephan
 *
 * @version $Revision: $ $Date: $
 */
@Path("/v1/classifications")
@MCRStaticContent
public class MCRRestAPIClassifications {

    private static Logger LOGGER = LogManager.getLogger(MCRRestAPIClassifications.class);

    public static final String FORMAT_JSON = "json";

    public static final String FORMAT_XML = "xml";

    private static final MCRCategoryDAO DAO = new MCRCategoryDAOImpl();

    /**
     *
     * @param info - a Jersey Context Object for URI
     *     Possible values are: json | xml (required)
     */
    @GET
    @Path("/")
    @Produces({ MediaType.TEXT_XML + ";charset=UTF-8", MediaType.APPLICATION_JSON + ";charset=UTF-8" })
    public Response listClassifications(@Context UriInfo info,
            @QueryParam("format") @DefaultValue("json") String format) {
        if (FORMAT_XML.equals(format)) {
            StringWriter sw = new StringWriter();

            XMLOutputter xout = new XMLOutputter(Format.getPrettyFormat());
            Document docOut = new Document();
            Element eRoot = new Element("mycoreclassifications");
            docOut.setRootElement(eRoot);

            for (MCRCategory cat : DAO.getRootCategories()) {
                eRoot.addContent(
                        new Element("mycoreclass").setAttribute("ID", cat.getId().getRootID()).setAttribute("href",
                                info.getAbsolutePathBuilder().path(cat.getId().getRootID()).build().toString()));
            }
            try {
                xout.output(docOut, sw);
                return Response.ok(sw.toString()).type("application/xml; charset=UTF-8").build();
            } catch (IOException e) {
                //ToDo
            }
        }

        if (FORMAT_JSON.equals(format)) {
            StringWriter sw = new StringWriter();
            try {
                JsonWriter writer = new JsonWriter(sw);
                writer.setIndent("    ");
                writer.beginObject();
                writer.name("mycoreclass");
                writer.beginArray();
                for (MCRCategory cat : DAO.getRootCategories()) {
                    writer.beginObject();
                    writer.name("ID").value(cat.getId().getRootID());
                    writer.name("href")
                            .value(info.getAbsolutePathBuilder().path(cat.getId().getRootID()).build().toString());
                    writer.endObject();
                }
                writer.endArray();
                writer.endObject();

                writer.close();

                return Response.ok(sw.toString()).type("application/json; charset=UTF-8").build();
            } catch (IOException e) {
                //toDo
            }
        }
        return Response.status(Status.BAD_REQUEST).build();
    }

    /**
     *  returns a single classification object
     *
     * @param classID - the classfication id
     * @param format
     *   Possible values are: json | xml (required)
     * @param filter
     *     a ';'-separated list of ':'-separated key-value pairs, possible keys are:
     *      - lang - the language of the returned labels, if ommited all labels in all languages will be returned
     *      - root - an id for a category which will be used as root
     *      - nonempty - hide empty categories
     * @param style
     *    a ';'-separated list of values, possible keys are:
     *      - 'checkboxtree' - create a json syntax which can be used as input for a dojo checkboxtree;
     *      - 'checked'   - (together with 'checkboxtree') all checkboxed will be checked
     *      - 'jstree' - create a json syntax which can be used as input for a jsTree
     *      - 'opened' - (together with 'jstree') - all nodes will be opened
     *      - 'disabled' - (together with 'jstree') - all nodes will be disabled
     *      - 'selected' - (together with 'jstree') - all nodes will be selected
     * @param callback used for JSONP - wrap json result into a Javascript function named by callback parameter
     * @return a Jersey Response object
     */
    @GET
    //@Path("/id/{value}{format:(\\.[^/]+?)?}")  -> working, but returns empty string instead of default value
    @Path("/{classID}")
    @Produces({ MediaType.TEXT_XML + ";charset=UTF-8", MediaType.APPLICATION_JSON + ";charset=UTF-8" })
    public Response showObject(@PathParam("classID") String classID,
            @QueryParam("format") @DefaultValue("xml") String format,
            @QueryParam("filter") @DefaultValue("") String filter,
            @QueryParam("style") @DefaultValue("") String style,
            @QueryParam("callback") @DefaultValue("") String callback) {
        String rootCateg = null;
        String lang = null;
        boolean filterNonEmpty = false;
        boolean filterNoChildren = false;

        for (String f : filter.split(";")) {
            if (f.startsWith("root:")) {
                rootCateg = f.substring(5);
            }
            if (f.startsWith("lang:")) {
                lang = f.substring(5);
            }
            if (f.startsWith("nonempty")) {
                filterNonEmpty = true;
            }
            if (f.startsWith("nochildren")) {
                filterNoChildren = true;
            }
        }

        if (format == null || classID == null) {
            return Response.serverError().status(Status.BAD_REQUEST).build();
            //TODO response.sendError(HttpServletResponse.SC_NOT_FOUND, "Please specify parameters format and classid.");
        }
        try {
            MCRCategory cl = DAO.getCategory(MCRCategoryID.rootID(classID), -1);
            if (cl == null) {
                return MCRRestAPIError.create(Response.Status.BAD_REQUEST, "Classification not found.",
                        "There is no classification with the given ID.").createHttpResponse();
            }
            Document docClass = MCRCategoryTransformer.getMetaDataDocument(cl, false);
            Element eRoot = docClass.getRootElement();
            if (rootCateg != null) {
                XPathExpression<Element> xpe = XPathFactory.instance()
                        .compile("//category[@ID='" + rootCateg + "']", Filters.element());
                Element e = xpe.evaluateFirst(docClass);
                if (e != null) {
                    eRoot = e;
                } else {
                    return MCRRestAPIError
                            .create(Response.Status.BAD_REQUEST, "Category not found.",
                                    "The classfication does not contain a category with the given ID.")
                            .createHttpResponse();
                }
            }
            if (filterNonEmpty) {
                Element eFilter = eRoot;
                if (eFilter.getName().equals("mycoreclass")) {
                    eFilter = eFilter.getChild("categories");
                }
                filterNonEmpty(docClass.getRootElement().getAttributeValue("ID"), eFilter);
            }
            if (filterNoChildren) {
                eRoot.removeChildren("category");
            }

            if (FORMAT_JSON.equals(format)) {
                String json = writeJSON(eRoot, lang, style);
                //eventually: allow Cross Site Requests: .header("Access-Control-Allow-Origin", "*")
                if (callback.length() > 0) {
                    return Response.ok(callback + "(" + json + ")").type("application/javascript; charset=UTF-8")
                            .build();
                } else {
                    return Response.ok(json).type("application/json; charset=UTF-8").build();
                }
            }

            if (FORMAT_XML.equals(format)) {
                String xml = writeXML(eRoot, lang);
                return Response.ok(xml).type("application/xml; charset=UTF-8").build();
            }
        } catch (Exception e) {
            LogManager.getLogger(this.getClass()).error("Error outputting classification", e);
            //TODO response.sendError(HttpServletResponse.SC_NOT_FOUND, "Error outputting classification");
        }
        return null;
    }

    /**
     * Output xml
     * @param eRoot - the root element
     * @param lang - the language which should be filtered or null for no filter
     * @return a string representation of the XML
     * @throws IOException
     */
    private static String writeXML(Element eRoot, String lang) throws IOException {
        StringWriter sw = new StringWriter();
        if (lang != null) {
            // <label xml:lang="en" text="part" />
            XPathExpression<Element> xpE = XPathFactory.instance().compile("//label[@xml:lang!='" + lang + "']",
                    Filters.element(), null, Namespace.XML_NAMESPACE);
            for (Element e : xpE.evaluate(eRoot)) {
                e.getParentElement().removeContent(e);
            }
        }
        XMLOutputter xout = new XMLOutputter(Format.getPrettyFormat());
        Document docOut = new Document(eRoot.detach());
        xout.output(docOut, sw);
        return sw.toString();
    }

    /**
     * Output JSON
     * @param eRoot - the category element
     * @param lang - the language to be filtered for or null if all languages should be displayed
     * @param style - the style
     * @return a string representation of a JSON object
     * @throws IOException
     */
    private String writeJSON(Element eRoot, String lang, String style) throws IOException {
        StringWriter sw = new StringWriter();
        JsonWriter writer = new JsonWriter(sw);
        writer.setIndent("  ");
        if (style.contains("checkboxtree")) {
            if (lang == null) {
                lang = "de";
            }
            writer.beginObject();
            writer.name("identifier").value(eRoot.getAttributeValue("ID"));
            for (Element eLabel : eRoot.getChildren("label")) {
                if (lang.equals(eLabel.getAttributeValue("lang", Namespace.XML_NAMESPACE))) {
                    writer.name("label").value(eLabel.getAttributeValue("text"));
                }
            }
            writer.name("items");

            writeChildrenAsJSONCBTree(eRoot = eRoot.getChild("categories"), writer, lang,
                    style.contains("checked"));
            writer.endObject();
        } else if (style.contains("jstree")) {
            if (lang == null) {
                lang = "de";
            }
            writeChildrenAsJSONJSTree(eRoot = eRoot.getChild("categories"), writer, lang, style.contains("opened"),
                    style.contains("disabled"), style.contains("selected"));
        } else {
            writer.beginObject(); // {
            writer.name("ID").value(eRoot.getAttributeValue("ID"));
            writer.name("label");
            writer.beginArray();
            for (Element eLabel : eRoot.getChildren("label")) {
                if (lang == null || lang.equals(eLabel.getAttributeValue("lang", Namespace.XML_NAMESPACE))) {
                    writer.beginObject();
                    writer.name("lang").value(eLabel.getAttributeValue("lang", Namespace.XML_NAMESPACE));
                    writer.name("text").value(eLabel.getAttributeValue("text"));
                    if (eLabel.getAttributeValue("description") != null) {
                        writer.name("description").value(eLabel.getAttributeValue("description"));
                    }
                    writer.endObject();
                }
            }
            writer.endArray();

            if (eRoot.equals(eRoot.getDocument().getRootElement())) {
                writeChildrenAsJSON(eRoot.getChild("categories"), writer, lang);
            } else {
                writeChildrenAsJSON(eRoot, writer, lang);
            }

            writer.endObject();
        }
        writer.close();
        return sw.toString();
    }

    /**
     * output categories in JSON format
     * @param eParent - the parent xml element
     * @param writer - the JSON writer
     * @param lang - the language to be filtered or null if all languages should be displayed
     *
     * @throws IOException
     */
    private static void writeChildrenAsJSON(Element eParent, JsonWriter writer, String lang) throws IOException {
        if (eParent.getChildren("category").size() == 0)
            return;

        writer.name("categories");
        writer.beginArray();
        for (Element e : eParent.getChildren("category")) {
            writer.beginObject();
            writer.name("ID").value(e.getAttributeValue("ID"));
            writer.name("labels").beginArray();
            for (Element eLabel : e.getChildren("label")) {
                if (lang == null || lang.equals(eLabel.getAttributeValue("lang", Namespace.XML_NAMESPACE))) {
                    writer.beginObject();
                    writer.name("lang").value(eLabel.getAttributeValue("lang", Namespace.XML_NAMESPACE));
                    writer.name("text").value(eLabel.getAttributeValue("text"));
                    if (eLabel.getAttributeValue("description") != null) {
                        writer.name("description").value(eLabel.getAttributeValue("description"));
                    }
                    writer.endObject();
                }
            }
            writer.endArray();

            if (e.getChildren("category").size() > 0) {
                writeChildrenAsJSON(e, writer, lang);
            }
            writer.endObject();
        }
        writer.endArray();
    }

    /**
     * output children in JSON format used as input for Dijit Checkbox Tree
     *
     * @param eParent - the parent xml element
     * @param writer - the JSON writer
     * @param lang - the language to be filtered or null if all languages should be displayed
     *
     * @throws IOException
     */
    private static void writeChildrenAsJSONCBTree(Element eParent, JsonWriter writer, String lang, boolean checked)
            throws IOException {
        writer.beginArray();
        for (Element e : eParent.getChildren("category")) {
            writer.beginObject();
            writer.name("ID").value(e.getAttributeValue("ID"));
            for (Element eLabel : e.getChildren("label")) {
                if (lang == null || lang.equals(eLabel.getAttributeValue("lang", Namespace.XML_NAMESPACE))) {
                    writer.name("text").value(eLabel.getAttributeValue("text"));
                }
            }
            writer.name("checked").value(checked);
            if (e.getChildren("category").size() > 0) {
                writer.name("children");
                writeChildrenAsJSONCBTree(e, writer, lang, checked);
            }
            writer.endObject();
        }
        writer.endArray();
    }

    /**
     * output children in JSON format used as input for a jsTree
     *
     * @param eParent - the parent xml element
     * @param writer - the JSON writer
     * @param lang - the language to be filtered or null if all languages should be displayed
     * @param opened - true, if all leaf nodes should be displayed
     * @param disabled - true, if all nodes should be disabled
     * @param selected - true, if all node should be selected
     *
     * @throws IOException
     */
    private static void writeChildrenAsJSONJSTree(Element eParent, JsonWriter writer, String lang, boolean opened,
            boolean disabled, boolean selected) throws IOException {
        writer.beginArray();
        for (Element e : eParent.getChildren("category")) {
            writer.beginObject();
            writer.name("id").value(e.getAttributeValue("ID"));
            for (Element eLabel : e.getChildren("label")) {
                if (lang == null || lang.equals(eLabel.getAttributeValue("lang", Namespace.XML_NAMESPACE))) {
                    writer.name("text").value(eLabel.getAttributeValue("text"));
                }
            }
            if (opened || disabled || selected) {
                writer.name("state");
                writer.beginObject();
                if (opened) {
                    writer.name("opened").value(true);
                }
                if (disabled) {
                    writer.name("disabled").value(true);
                }
                if (selected) {
                    writer.name("selected").value(true);
                }
                writer.endObject();
            }
            if (e.getChildren("category").size() > 0) {
                writer.name("children");
                writeChildrenAsJSONJSTree(e, writer, lang, opened, disabled, selected);
            }
            writer.endObject();
        }
        writer.endArray();
    }

    private void filterNonEmpty(String classId, Element e) {
        SolrClient solrClient = MCRSolrClientFactory.getSolrClient();
        for (int i = 0; i < e.getChildren("category").size(); i++) {
            Element cat = e.getChildren("category").get(i);

            SolrQuery solrQquery = new SolrQuery();
            solrQquery.setQuery("category:\""
                    + MCRSolrUtils.escapeSearchValue(classId + ":" + cat.getAttributeValue("ID")) + "\"");
            solrQquery.setRows(0);
            try {
                QueryResponse response = solrClient.query(solrQquery);
                SolrDocumentList solrResults = response.getResults();
                if (solrResults.getNumFound() == 0) {
                    e.removeContent(cat);
                    i--;
                }
            } catch (SolrServerException | IOException exc) {
                LOGGER.error(exc);
            }
        }
        for (int i = 0; i < e.getChildren("category").size(); i++) {
            filterNonEmpty(classId, e.getChildren("category").get(i));
        }
    }
}