org.structr.rest.servlet.JsonRestServlet.java Source code

Java tutorial

Introduction

Here is the source code for org.structr.rest.servlet.JsonRestServlet.java

Source

/**
 * Copyright (C) 2010-2016 Structr GmbH
 *
 * This file is part of Structr <http://structr.org>.
 *
 * Structr 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.
 *
 * Structr 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 Structr.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.structr.rest.servlet;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSyntaxException;
import java.io.IOException;
import java.io.Writer;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.neo4j.kernel.DeadlockDetectedException;
import org.structr.common.PagingHelper;
import org.structr.common.SecurityContext;
import org.structr.common.error.FrameworkException;
import org.structr.core.*;
import org.structr.core.app.App;
import org.structr.core.app.StructrApp;
import org.structr.core.auth.Authenticator;
import org.structr.core.entity.AbstractNode;
import org.structr.core.graph.NodeFactory;
import org.structr.core.graph.Tx;
import org.structr.core.graph.search.SearchCommand;
import org.structr.core.property.PropertyKey;
import org.structr.rest.JsonInputGSONAdapter;
import org.structr.rest.ResourceProvider;
import org.structr.rest.RestMethodResult;
import org.structr.rest.adapter.FrameworkExceptionGSONAdapter;
import org.structr.rest.adapter.ResultGSONAdapter;
import org.structr.rest.resource.Resource;
import org.structr.rest.serialization.StreamingHtmlWriter;
import org.structr.rest.serialization.StreamingJsonWriter;
import org.structr.rest.service.HttpServiceServlet;
import org.structr.rest.service.StructrHttpServiceConfig;
import org.tuckey.web.filters.urlrewrite.utils.StringUtils;

//~--- classes ----------------------------------------------------------------

/**
 * Implements the structr REST API.
 *
 *
 */
public class JsonRestServlet extends HttpServlet implements HttpServiceServlet {

    public static final int DEFAULT_VALUE_PAGE_SIZE = 20;
    public static final String DEFAULT_VALUE_SORT_ORDER = "asc";
    public static final String REQUEST_PARAMETER_LOOSE_SEARCH = "loose";
    public static final String REQUEST_PARAMETER_PAGE_NUMBER = "page";
    public static final String REQUEST_PARAMETER_PAGE_SIZE = "pageSize";
    public static final String REQUEST_PARAMETER_OFFSET_ID = "pageStartId";
    public static final String REQUEST_PARAMETER_SORT_KEY = "sort";
    public static final String REQUEST_PARAMETER_SORT_ORDER = "order";
    public static final Set<String> commonRequestParameters = new LinkedHashSet<>();
    private static final Logger logger = Logger.getLogger(JsonRestServlet.class.getName());

    static {

        commonRequestParameters.add(REQUEST_PARAMETER_LOOSE_SEARCH);
        commonRequestParameters.add(REQUEST_PARAMETER_PAGE_NUMBER);
        commonRequestParameters.add(REQUEST_PARAMETER_PAGE_SIZE);
        commonRequestParameters.add(REQUEST_PARAMETER_OFFSET_ID);
        commonRequestParameters.add(REQUEST_PARAMETER_SORT_KEY);
        commonRequestParameters.add(REQUEST_PARAMETER_SORT_ORDER);
        commonRequestParameters.add("debugLoggingEnabled");

        // cross reference here, but these need to be added as well..
        commonRequestParameters.add(SearchCommand.DISTANCE_SEARCH_KEYWORD);
        commonRequestParameters.add(SearchCommand.LOCATION_SEARCH_KEYWORD);
        commonRequestParameters.add(SearchCommand.STREET_SEARCH_KEYWORD);
        commonRequestParameters.add(SearchCommand.HOUSE_SEARCH_KEYWORD);
        commonRequestParameters.add(SearchCommand.POSTAL_CODE_SEARCH_KEYWORD);
        commonRequestParameters.add(SearchCommand.CITY_SEARCH_KEYWORD);
        commonRequestParameters.add(SearchCommand.STATE_SEARCH_KEYWORD);
        commonRequestParameters.add(SearchCommand.COUNTRY_SEARCH_KEYWORD);
    }

    // final fields
    private final Map<Pattern, Class<? extends Resource>> resourceMap = new LinkedHashMap<>();
    private final StructrHttpServiceConfig config = new StructrHttpServiceConfig();

    // non-final fields
    private Value<String> propertyView = null;
    private ThreadLocalGson gson = null;
    private boolean indentJson = true;

    //~--- methods --------------------------------------------------------

    @Override
    public StructrHttpServiceConfig getConfig() {
        return config;
    }

    @Override
    public void init() {

        try {
            indentJson = Boolean.parseBoolean(StructrApp.getConfigurationValue(Services.JSON_INDENTATION, "true"));

        } catch (Throwable t) {

            logger.log(Level.WARNING, "Unable to parse value for {0}: {1}",
                    new Object[] { Services.JSON_INDENTATION, t.getMessage() });
        }

        // inject resources
        final ResourceProvider provider = config.getResourceProvider();
        if (provider != null) {

            resourceMap.putAll(provider.getResources());

        } else {

            logger.log(Level.SEVERE,
                    "Unable to initialize JsonRestServlet, no resource provider found. Please check structr.conf for a valid resource provider class.");
        }

        // initialize variables
        this.propertyView = new ThreadLocalPropertyView();
        this.gson = new ThreadLocalGson(config.getOutputNestingDepth());
    }

    // <editor-fold defaultstate="collapsed" desc="DELETE">
    @Override
    protected void doDelete(final HttpServletRequest request, final HttpServletResponse response)
            throws ServletException, IOException {

        SecurityContext securityContext = null;
        Authenticator authenticator = null;
        RestMethodResult result = null;
        Resource resource = null;

        try {

            // first thing to do!
            request.setCharacterEncoding("UTF-8");
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8;");

            // isolate request authentication in a transaction
            try (final Tx tx = StructrApp.getInstance().tx()) {
                authenticator = config.getAuthenticator();
                securityContext = authenticator.initializeAndExamineRequest(request, response);
                tx.success();
            }

            final App app = StructrApp.getInstance(securityContext);

            // isolate resource authentication
            try (final Tx tx = app.tx()) {

                resource = ResourceHelper.optimizeNestedResourceChain(
                        ResourceHelper.parsePath(securityContext, request, resourceMap, propertyView));
                authenticator.checkResourceAccess(securityContext, request, resource.getResourceSignature(),
                        propertyView.get(securityContext));

                tx.success();
            }

            // isolate doDelete
            boolean retry = true;
            while (retry) {

                try (final Tx tx = app.tx()) {
                    result = resource.doDelete();
                    tx.success();
                    retry = false;

                } catch (DeadlockDetectedException ddex) {
                    retry = true;
                }
            }

            // isolate write output
            try (final Tx tx = app.tx()) {
                result.commitResponse(gson.get(), response);
                tx.success();
            }

        } catch (FrameworkException frameworkException) {

            // set status & write JSON output
            response.setStatus(frameworkException.getStatus());
            gson.get().toJson(frameworkException, response.getWriter());
            response.getWriter().println();

        } catch (JsonSyntaxException jsex) {

            logger.log(Level.WARNING, "JsonSyntaxException in DELETE", jsex);

            int code = HttpServletResponse.SC_BAD_REQUEST;

            response.setStatus(code);
            response.getWriter().append(
                    RestMethodResult.jsonError(code, "JsonSyntaxException in DELETE: " + jsex.getMessage()));

        } catch (JsonParseException jpex) {

            logger.log(Level.WARNING, "JsonParseException in DELETE", jpex);

            int code = HttpServletResponse.SC_BAD_REQUEST;

            response.setStatus(code);
            response.getWriter().append(
                    RestMethodResult.jsonError(code, "JsonSyntaxException in DELETE: " + jpex.getMessage()));

        } catch (Throwable t) {

            logger.log(Level.WARNING, "Exception in DELETE", t);

            int code = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;

            response.setStatus(code);
            response.getWriter()
                    .append(RestMethodResult.jsonError(code, "JsonSyntaxException in DELETE: " + t.getMessage()));

        } finally {

            try {
                //response.getWriter().flush();
                response.getWriter().close();

            } catch (IOException t) {

                logger.log(Level.WARNING, "Unable to flush and close response: {0}", t.getMessage());
            }

        }
    }

    // </editor-fold>

    @Override
    protected void doGet(final HttpServletRequest request, final HttpServletResponse response)
            throws ServletException, IOException {

        final boolean returnContent = true;

        doGetOrHead(request, response, returnContent);
    }

    @Override
    protected void doHead(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        final boolean returnContent = false;

        doGetOrHead(request, response, returnContent);
    }

    // <editor-fold defaultstate="collapsed" desc="OPTIONS">
    @Override
    protected void doOptions(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        final SecurityContext securityContext;
        final Authenticator authenticator;
        final Resource resource;

        RestMethodResult result = new RestMethodResult(HttpServletResponse.SC_BAD_REQUEST);

        try {

            // first thing to do!
            request.setCharacterEncoding("UTF-8");
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8;");

            // isolate request authentication in a transaction
            try (final Tx tx = StructrApp.getInstance().tx()) {
                authenticator = config.getAuthenticator();
                securityContext = authenticator.initializeAndExamineRequest(request, response);
                tx.success();
            }

            final App app = StructrApp.getInstance(securityContext);

            // isolate resource authentication
            try (final Tx tx = app.tx()) {

                resource = ResourceHelper.applyViewTransformation(request, securityContext,
                        ResourceHelper.optimizeNestedResourceChain(
                                ResourceHelper.parsePath(securityContext, request, resourceMap, propertyView)),
                        propertyView);
                authenticator.checkResourceAccess(securityContext, request, resource.getResourceSignature(),
                        propertyView.get(securityContext));
                tx.success();
            }

            // isolate doOptions
            boolean retry = true;
            while (retry) {

                try (final Tx tx = app.tx()) {

                    result = resource.doOptions();
                    tx.success();
                    retry = false;

                } catch (DeadlockDetectedException ddex) {
                    retry = true;
                }
            }

            // isolate write output
            try (final Tx tx = app.tx()) {
                result.commitResponse(gson.get(), response);
                tx.success();
            }

        } catch (FrameworkException frameworkException) {

            // set status & write JSON output
            response.setStatus(frameworkException.getStatus());
            gson.get().toJson(frameworkException, response.getWriter());
            response.getWriter().println();

        } catch (JsonSyntaxException jsex) {

            logger.log(Level.WARNING, "JsonSyntaxException in OPTIONS", jsex);

            int code = HttpServletResponse.SC_BAD_REQUEST;

            response.setStatus(code);
            response.getWriter().append(
                    RestMethodResult.jsonError(code, "JsonSyntaxException in OPTIONS: " + jsex.getMessage()));

        } catch (JsonParseException jpex) {

            logger.log(Level.WARNING, "JsonParseException in OPTIONS", jpex);

            int code = HttpServletResponse.SC_BAD_REQUEST;

            response.setStatus(code);
            response.getWriter().append(
                    RestMethodResult.jsonError(code, "JsonSyntaxException in OPTIONS: " + jpex.getMessage()));

        } catch (Throwable t) {

            logger.log(Level.WARNING, "Exception in OPTIONS", t);

            int code = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;

            response.setStatus(code);
            response.getWriter()
                    .append(RestMethodResult.jsonError(code, "JsonSyntaxException in OPTIONS: " + t.getMessage()));

        } finally {

            try {
                //response.getWriter().flush();
                response.getWriter().close();

            } catch (Throwable t) {

                logger.log(Level.WARNING, "Unable to flush and close response: {0}", t.getMessage());
            }

        }
    }

    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="POST">
    @Override
    protected void doPost(final HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        final List<RestMethodResult> results = new LinkedList<>();
        final SecurityContext securityContext;
        final Authenticator authenticator;
        final Resource resource;

        try {

            // first thing to do!
            request.setCharacterEncoding("UTF-8");
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8;");

            // isolate request authentication in a transaction
            try (final Tx tx = StructrApp.getInstance().tx()) {
                authenticator = config.getAuthenticator();
                securityContext = authenticator.initializeAndExamineRequest(request, response);
                tx.success();
            }

            final App app = StructrApp.getInstance(securityContext);
            final String input = IOUtils.toString(request.getReader());
            final IJsonInput jsonInput = cleanAndParseJsonString(app, input);

            if (securityContext != null) {

                // isolate resource authentication
                try (final Tx tx = app.tx()) {

                    resource = ResourceHelper.applyViewTransformation(request, securityContext,
                            ResourceHelper.optimizeNestedResourceChain(
                                    ResourceHelper.parsePath(securityContext, request, resourceMap, propertyView)),
                            propertyView);
                    authenticator.checkResourceAccess(securityContext, request, resource.getResourceSignature(),
                            propertyView.get(securityContext));
                    tx.success();
                }

                // isolate doPost
                boolean retry = true;
                while (retry) {

                    if (resource.createPostTransaction()) {

                        try (final Tx tx = app.tx()) {

                            for (JsonInput propertySet : jsonInput.getJsonInputs()) {

                                results.add(resource.doPost(convertPropertySetToMap(propertySet)));
                            }

                            tx.success();
                            retry = false;

                        } catch (DeadlockDetectedException ddex) {
                            retry = true;
                        }

                    } else {

                        try {

                            for (JsonInput propertySet : jsonInput.getJsonInputs()) {

                                results.add(resource.doPost(convertPropertySetToMap(propertySet)));
                            }

                            retry = false;

                        } catch (DeadlockDetectedException ddex) {
                            retry = true;
                        }
                    }
                }

                // set default value for property view
                propertyView.set(securityContext, config.getDefaultPropertyView());

                // isolate write output
                try (final Tx tx = app.tx()) {

                    if (!results.isEmpty()) {

                        final RestMethodResult result = results.get(0);
                        final int resultCount = results.size();

                        if (result != null) {

                            if (resultCount > 1) {

                                for (final RestMethodResult r : results) {

                                    final GraphObject objectCreated = r.getContent().get(0);
                                    if (!result.getContent().contains(objectCreated)) {

                                        result.addContent(objectCreated);
                                    }

                                }

                                // remove Location header if more than one object was
                                // written because it may only contain a single URL
                                result.addHeader("Location", null);
                            }

                            result.commitResponse(gson.get(), response);
                        }

                    }

                    tx.success();
                }

            } else {

                // isolate write output
                try (final Tx tx = app.tx()) {

                    new RestMethodResult(HttpServletResponse.SC_FORBIDDEN).commitResponse(gson.get(), response);
                    tx.success();
                }

            }

        } catch (FrameworkException frameworkException) {

            // set status & write JSON output
            response.setStatus(frameworkException.getStatus());
            gson.get().toJson(frameworkException, response.getWriter());
            response.getWriter().println();

        } catch (JsonSyntaxException jsex) {

            logger.log(Level.WARNING, "POST: Invalid JSON syntax", jsex.getMessage());

            int code = HttpServletResponse.SC_BAD_REQUEST;

            response.setStatus(code);
            response.getWriter()
                    .append(RestMethodResult.jsonError(code, "JsonSyntaxException in POST: " + jsex.getMessage()));

        } catch (JsonParseException jpex) {

            logger.log(Level.WARNING, "Unable to parse JSON string", jpex.getMessage());

            int code = HttpServletResponse.SC_BAD_REQUEST;

            response.setStatus(code);
            response.getWriter()
                    .append(RestMethodResult.jsonError(code, "JsonParseException in POST: " + jpex.getMessage()));

        } catch (UnsupportedOperationException uoe) {

            logger.log(Level.WARNING, "POST not supported");

            int code = HttpServletResponse.SC_BAD_REQUEST;

            response.setStatus(code);
            response.getWriter()
                    .append(RestMethodResult.jsonError(code, "POST not supported: " + uoe.getMessage()));

        } catch (Throwable t) {

            logger.log(Level.WARNING, "Exception in POST", t);

            int code = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;

            response.setStatus(code);
            response.getWriter()
                    .append(RestMethodResult.jsonError(code, "JsonSyntaxException in POST: " + t.getMessage()));

        } finally {

            try {
                //response.getWriter().flush();
                response.getWriter().close();

            } catch (Throwable t) {

                logger.log(Level.WARNING, "Unable to flush and close response: {0}", t.getMessage());
            }

        }
    }

    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="PUT">
    @Override
    protected void doPut(final HttpServletRequest request, final HttpServletResponse response)
            throws ServletException, IOException {

        final SecurityContext securityContext;
        final Authenticator authenticator;
        final Resource resource;

        RestMethodResult result = new RestMethodResult(HttpServletResponse.SC_BAD_REQUEST);

        try {

            // first thing to do!
            request.setCharacterEncoding("UTF-8");
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8;");

            // isolate request authentication in a transaction
            try (final Tx tx = StructrApp.getInstance().tx()) {
                authenticator = config.getAuthenticator();
                securityContext = authenticator.initializeAndExamineRequest(request, response);
                tx.success();
            }

            final App app = StructrApp.getInstance(securityContext);
            final String input = IOUtils.toString(request.getReader());
            final IJsonInput jsonInput = cleanAndParseJsonString(app, input);

            if (securityContext != null) {

                // isolate resource authentication
                try (final Tx tx = app.tx()) {

                    // evaluate constraint chain
                    resource = ResourceHelper.applyViewTransformation(request, securityContext,
                            ResourceHelper.optimizeNestedResourceChain(
                                    ResourceHelper.parsePath(securityContext, request, resourceMap, propertyView)),
                            propertyView);
                    authenticator.checkResourceAccess(securityContext, request, resource.getResourceSignature(),
                            propertyView.get(securityContext));
                    tx.success();
                }

                // isolate doPut
                boolean retry = true;
                while (retry) {

                    try (final Tx tx = app.tx()) {
                        result = resource.doPut(convertPropertySetToMap(jsonInput.getJsonInputs().get(0)));
                        tx.success();
                        retry = false;

                    } catch (DeadlockDetectedException ddex) {
                        retry = true;
                    }
                }

                // isolate write output
                try (final Tx tx = app.tx()) {
                    result.commitResponse(gson.get(), response);
                    tx.success();
                }

            } else {

                // isolate write output
                try (final Tx tx = app.tx()) {
                    result = new RestMethodResult(HttpServletResponse.SC_FORBIDDEN);
                    result.commitResponse(gson.get(), response);
                    tx.success();
                }

            }

        } catch (FrameworkException frameworkException) {

            // set status & write JSON output
            response.setStatus(frameworkException.getStatus());
            gson.get().toJson(frameworkException, response.getWriter());
            response.getWriter().println();

        } catch (JsonSyntaxException jsex) {

            logger.log(Level.WARNING, "PUT: Invalid JSON syntax", jsex.getMessage());

            int code = HttpServletResponse.SC_BAD_REQUEST;

            response.setStatus(code);
            response.getWriter()
                    .append(RestMethodResult.jsonError(code, "JsonSyntaxException in PUT: " + jsex.getMessage()));

        } catch (JsonParseException jpex) {

            logger.log(Level.WARNING, "PUT: Unable to parse JSON string", jpex.getMessage());

            int code = HttpServletResponse.SC_BAD_REQUEST;

            response.setStatus(code);
            response.getWriter()
                    .append(RestMethodResult.jsonError(code, "JsonSyntaxException in PUT: " + jpex.getMessage()));

        } catch (Throwable t) {

            logger.log(Level.WARNING, "Exception in PUT", t);

            int code = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;

            response.setStatus(code);
            response.getWriter()
                    .append(RestMethodResult.jsonError(code, "JsonSyntaxException in PUT: " + t.getMessage()));

        } finally {

            try {
                //response.getWriter().flush();
                response.getWriter().close();

            } catch (Throwable t) {

                logger.log(Level.WARNING, "Unable to flush and close response: {0}", t.getMessage());
            }

        }
    }

    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="TRACE">
    @Override
    protected void doTrace(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        //      logRequest("TRACE", request);
        response.setContentType("application/json; charset=UTF-8;");
        response.setCharacterEncoding("UTF-8");

        int code = HttpServletResponse.SC_METHOD_NOT_ALLOWED;

        response.setStatus(code);
        response.getWriter().append(RestMethodResult.jsonError(code, "TRACE method not allowed"));
    }

    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="private methods">

    private IJsonInput cleanAndParseJsonString(final App app, final String input) throws FrameworkException {

        IJsonInput jsonInput;

        // isolate input parsing (will include read and write operations)
        try (final Tx tx = app.tx()) {
            jsonInput = gson.get().fromJson(input, IJsonInput.class);
            tx.success();
        }

        if (jsonInput == null) {

            if (StringUtils.isBlank(input)) {

                try (final Tx tx = app.tx()) {
                    jsonInput = gson.get().fromJson("{}", IJsonInput.class);
                    tx.success();
                }

            } else {
                //throw new JsonParseException("Invalid or empty JSON string, must at least contain {} to be valid!");
                jsonInput = new JsonSingleInput();
            }
        }

        return jsonInput;

    }

    private Map<String, Object> convertPropertySetToMap(JsonInput propertySet) {

        if (propertySet != null) {
            return propertySet.getAttributes();
        }

        return new LinkedHashMap<>();
    }

    private void doGetOrHead(final HttpServletRequest request, final HttpServletResponse response,
            final boolean returnContent) throws ServletException, IOException {

        SecurityContext securityContext = null;
        Authenticator authenticator = null;
        Result result = null;
        Resource resource = null;

        try {

            // first thing to do!
            request.setCharacterEncoding("UTF-8");
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8;");

            // isolate request authentication in a transaction
            try (final Tx tx = StructrApp.getInstance().tx()) {
                authenticator = config.getAuthenticator();
                securityContext = authenticator.initializeAndExamineRequest(request, response);
                tx.success();
            }

            final App app = StructrApp.getInstance(securityContext);

            // set default value for property view
            propertyView.set(securityContext, config.getDefaultPropertyView());

            // evaluate constraints and measure query time
            double queryTimeStart = System.nanoTime();

            // isolate resource authentication
            try (final Tx tx = app.tx()) {

                resource = ResourceHelper.applyViewTransformation(request, securityContext,
                        ResourceHelper.optimizeNestedResourceChain(
                                ResourceHelper.parsePath(securityContext, request, resourceMap, propertyView)),
                        propertyView);
                authenticator.checkResourceAccess(securityContext, request, resource.getResourceSignature(),
                        propertyView.get(securityContext));
                tx.success();
            }

            // add sorting & paging
            String pageSizeParameter = request.getParameter(REQUEST_PARAMETER_PAGE_SIZE);
            String pageParameter = request.getParameter(REQUEST_PARAMETER_PAGE_NUMBER);
            String offsetId = request.getParameter(REQUEST_PARAMETER_OFFSET_ID);
            String sortOrder = request.getParameter(REQUEST_PARAMETER_SORT_ORDER);
            String sortKeyName = request.getParameter(REQUEST_PARAMETER_SORT_KEY);
            boolean sortDescending = (sortOrder != null && "desc".equals(sortOrder.toLowerCase()));
            int pageSize = Services.parseInt(pageSizeParameter, NodeFactory.DEFAULT_PAGE_SIZE);
            int page = Services.parseInt(pageParameter, NodeFactory.DEFAULT_PAGE);
            String baseUrl = request.getRequestURI();
            PropertyKey sortKey = null;

            // set sort key
            if (sortKeyName != null) {

                Class<? extends GraphObject> type = resource.getEntityClass();
                if (type == null) {

                    // fallback to default implementation
                    // if no type can be determined
                    type = AbstractNode.class;
                }

                sortKey = StructrApp.getConfiguration().getPropertyKeyForDatabaseName(type, sortKeyName, false);
            }

            // isolate doGet
            boolean retry = true;
            while (retry) {

                try (final Tx tx = app.tx()) {
                    result = resource.doGet(sortKey, sortDescending, pageSize, page, offsetId);
                    tx.success();
                    retry = false;

                } catch (DeadlockDetectedException ddex) {
                    retry = true;
                }
            }

            if (returnContent) {

                result.setIsCollection(resource.isCollectionResource());
                result.setIsPrimitiveArray(resource.isPrimitiveArray());

                PagingHelper.addPagingParameter(result, pageSize, page);

                // timing..
                double queryTimeEnd = System.nanoTime();

                // store property view that will be used to render the results
                result.setPropertyView(propertyView.get(securityContext));

                // allow resource to modify result set
                resource.postProcessResultSet(result);

                DecimalFormat decimalFormat = new DecimalFormat("0.000000000",
                        DecimalFormatSymbols.getInstance(Locale.ENGLISH));
                result.setQueryTime(decimalFormat.format((queryTimeEnd - queryTimeStart) / 1000000000.0));

                String accept = request.getHeader("Accept");

                if (accept != null && accept.contains("text/html")) {

                    final StreamingHtmlWriter htmlStreamer = new StreamingHtmlWriter(this.propertyView, indentJson,
                            config.getOutputNestingDepth());

                    // isolate write output
                    try (final Tx tx = app.tx()) {

                        response.setContentType("text/html; charset=utf-8;");

                        try (final Writer writer = response.getWriter()) {

                            htmlStreamer.stream(securityContext, writer, result, baseUrl);
                            writer.append("\n"); // useful newline
                        }

                        tx.success();
                    }

                } else {

                    final StreamingJsonWriter jsonStreamer = new StreamingJsonWriter(this.propertyView, indentJson,
                            config.getOutputNestingDepth());

                    // isolate write output
                    try (final Tx tx = app.tx()) {

                        response.setContentType("application/json; charset=utf-8;");
                        try (final Writer writer = response.getWriter()) {

                            jsonStreamer.stream(securityContext, writer, result, baseUrl);
                            writer.append("\n"); // useful newline
                        }

                        tx.success();
                    }

                }
            }

            response.setStatus(HttpServletResponse.SC_OK);

        } catch (FrameworkException frameworkException) {

            // set status & write JSON output
            response.setStatus(frameworkException.getStatus());
            gson.get().toJson(frameworkException, response.getWriter());
            response.getWriter().println();

        } catch (JsonSyntaxException jsex) {

            logger.log(Level.WARNING, "JsonSyntaxException in GET", jsex);

            int code = HttpServletResponse.SC_BAD_REQUEST;

            response.setStatus(code);
            response.getWriter()
                    .append(RestMethodResult.jsonError(code, "Json syntax exception in GET: " + jsex.getMessage()));

        } catch (JsonParseException jpex) {

            logger.log(Level.WARNING, "JsonParseException in GET", jpex);

            int code = HttpServletResponse.SC_BAD_REQUEST;

            response.setStatus(code);
            response.getWriter()
                    .append(RestMethodResult.jsonError(code, "Parser exception in GET: " + jpex.getMessage()));

        } catch (Throwable t) {

            logger.log(Level.WARNING, "Exception in GET", t);

            int code = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;

            response.setStatus(code);
            response.getWriter().append(RestMethodResult.jsonError(code, "Exception in GET: " + t.getMessage()));

        } finally {

            try {
                //response.getWriter().flush();
                response.getWriter().close();

            } catch (Throwable t) {

                logger.log(Level.WARNING, "Unable to flush and close response: {0}", t.getMessage());
            }

        }
    }

    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="nested classes">
    private class ThreadLocalPropertyView extends ThreadLocal<String> implements Value<String> {

        @Override
        protected String initialValue() {
            return config.getDefaultPropertyView();
        }

        @Override
        public void set(SecurityContext securityContext, String value) {
            set(value);
        }

        @Override
        public String get(SecurityContext securityContext) {
            return get();
        }
    }

    public class ThreadLocalGson extends ThreadLocal<Gson> {

        private int outputNestingDepth = 3;

        public ThreadLocalGson(final int outputNestingDepth) {
            this.outputNestingDepth = outputNestingDepth;
        }

        @Override
        protected Gson initialValue() {

            ResultGSONAdapter resultGsonAdapter = new ResultGSONAdapter(propertyView, outputNestingDepth);
            JsonInputGSONAdapter jsonInputAdapter = new JsonInputGSONAdapter();

            // create GSON serializer
            final GsonBuilder gsonBuilder = new GsonBuilder().setPrettyPrinting().serializeNulls()
                    .registerTypeHierarchyAdapter(FrameworkException.class, new FrameworkExceptionGSONAdapter())
                    .registerTypeAdapter(IJsonInput.class, jsonInputAdapter)
                    .registerTypeAdapter(Result.class, resultGsonAdapter);

            final boolean lenient = Boolean.parseBoolean(StructrApp.getConfigurationValue("json.lenient", "false"));
            if (lenient) {

                // Serializes NaN, -Infinity, Infinity, see http://code.google.com/p/google-gson/issues/detail?id=378
                gsonBuilder.serializeSpecialFloatingPointValues();

            }

            return gsonBuilder.create();
        }
    }
    // </editor-fold>
}