org.jahia.bin.Find.java Source code

Java tutorial

Introduction

Here is the source code for org.jahia.bin.Find.java

Source

/**
 * ==========================================================================================
 * =                   JAHIA'S DUAL LICENSING - IMPORTANT INFORMATION                       =
 * ==========================================================================================
 *
 *                                 http://www.jahia.com
 *
 *     Copyright (C) 2002-2017 Jahia Solutions Group SA. All rights reserved.
 *
 *     THIS FILE IS AVAILABLE UNDER TWO DIFFERENT LICENSES:
 *     1/GPL OR 2/JSEL
 *
 *     1/ GPL
 *     ==================================================================================
 *
 *     IF YOU DECIDE TO CHOOSE THE GPL LICENSE, YOU MUST COMPLY WITH THE FOLLOWING TERMS:
 *
 *     This program 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.
 *
 *     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. If not, see <http://www.gnu.org/licenses/>.
 *
 *
 *     2/ JSEL - Commercial and Supported Versions of the program
 *     ===================================================================================
 *
 *     IF YOU DECIDE TO CHOOSE THE JSEL LICENSE, YOU MUST COMPLY WITH THE FOLLOWING TERMS:
 *
 *     Alternatively, commercial and supported versions of the program - also known as
 *     Enterprise Distributions - must be used in accordance with the terms and conditions
 *     contained in a separate written agreement between you and Jahia Solutions Group SA.
 *
 *     If you are unsure which license is appropriate for your use,
 *     please contact the sales department at sales@jahia.com.
 */
package org.jahia.bin;

import java.io.IOException;
import java.util.*;
import java.util.regex.Pattern;

import javax.jcr.*;
import javax.jcr.query.InvalidQueryException;
import javax.jcr.query.Query;
import javax.jcr.query.QueryManager;
import javax.jcr.query.QueryResult;
import javax.jcr.query.Row;
import javax.jcr.query.RowIterator;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.jahia.services.render.URLResolverFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.lucene.queryParser.QueryParser;
import org.jahia.api.Constants;
import org.jahia.exceptions.JahiaForbiddenAccessException;
import org.jahia.services.content.JCRContentUtils;
import org.jahia.services.content.JCRNodeWrapper;
import org.jahia.services.content.JCRPropertyWrapper;
import org.jahia.services.content.JCRSessionFactory;
import org.jahia.services.render.RenderException;
import org.jahia.services.render.URLResolver;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

/**
 * A small servlet to allow us to perform queries on the JCR.
 * @author loom
 * Date: Jan 26, 2010
 * Time: 5:55:17 PM
 */
public class Find extends BaseFindController {

    private static Logger logger = LoggerFactory.getLogger(Find.class);

    private int defaultDepthLimit = 1;

    private boolean defaultEscapeColon = false;

    private boolean defaultRemoveDuplicatePropertyValues = false;

    private URLResolverFactory urlResolverFactory;

    private Set<String> nodeTypesToSkip;

    private Set<String> pathsToSkip;

    private Set<String> propertiesToSkip;

    private Map<String, Set<String>> propertiesToSkipByNodeType;

    private static Set<String> toSet(String source) {
        return source != null && source.length() > 0
                ? new LinkedHashSet<String>(Arrays.asList(StringUtils.split(source, ", ")))
                : null;
    }

    public void setUrlResolverFactory(URLResolverFactory urlResolverFactory) {
        this.urlResolverFactory = urlResolverFactory;
    }

    private int getInt(String paramName, int defaultValue, HttpServletRequest req) throws IllegalArgumentException {
        int param = defaultValue;
        String valueStr = req.getParameter(paramName);
        if (StringUtils.isNotEmpty(valueStr)) {
            try {
                param = Integer.parseInt(valueStr);
            } catch (NumberFormatException nfe) {
                throw new IllegalArgumentException(
                        "Invalid integer value '" + valueStr + "' for request parameter '" + paramName + "'", nfe);
            }
        }

        return param;
    }

    private Query getQuery(HttpServletRequest request, HttpServletResponse response, String workspace,
            Locale locale) throws IOException, RepositoryException {

        QueryManager qm = JCRSessionFactory.getInstance().getCurrentUserSession(workspace, locale).getWorkspace()
                .getQueryManager();
        if (qm == null) {
            response.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
            return null;
        }

        String query = request.getParameter("query");
        if (StringUtils.isEmpty(query)) {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST,
                    "Mandatory parameter 'query' is not found in the request");
            return null;
        }

        // now let's parse the query to see if it references any other request parameters, and replace the reference with
        // the actual value.

        query = expandRequestMarkers(request, query, true,
                StringUtils.defaultIfEmpty(request.getParameter("language"), Query.JCR_SQL2), false);
        logger.debug("Using expanded query=[{}]", query);

        Query q = qm.createQuery(query,
                StringUtils.defaultIfEmpty(request.getParameter("language"), Query.JCR_SQL2));

        int limit = getInt("limit", defaultLimit, request);
        if (limit <= 0 || limit > hardLimit) {
            limit = hardLimit;
        }

        int offset = getInt("offset", 0, request);

        if (limit > 0) {
            q.setLimit(limit);
        }
        if (offset > 0) {
            q.setOffset(offset);
        }

        return q;
    }

    protected String expandRequestMarkers(HttpServletRequest request, String sourceString, boolean escapeValue,
            String queryLanguage, boolean escapeForRegexp) {
        String result = new String(sourceString);
        int refMarkerPos = result.indexOf("{$");
        while (refMarkerPos >= 0) {
            int endRefMarkerPos = result.indexOf("}", refMarkerPos);
            if (endRefMarkerPos > 0) {
                String refName = result.substring(refMarkerPos + 2, endRefMarkerPos);
                String refValue = request.getParameter(refName);
                if (refValue != null) {
                    // now it's very important that we escape it properly to avoid injection security holes
                    if (escapeValue) {
                        refValue = QueryParser.escape(refValue);
                        if (Query.XPATH.equals(queryLanguage)) {
                            // found this here : http://markmail.org/thread/pd7myawyv2dadmdh
                            refValue = StringUtils.replace(refValue, "'", "\\'");
                        } else {
                        }
                        refValue = StringUtils.replace(refValue, "'", "''");
                    }
                    if (escapeForRegexp) {
                        refValue = Pattern.quote(refValue);
                    }
                    result = StringUtils.replace(result, "{$" + refName + "}", refValue);
                } else {
                    // the request parameter wasn't found, so we leave the marker as it is, simply ignoring it.
                }
            }
            refMarkerPos = result.indexOf("{$", refMarkerPos + 2);
        }
        return result;
    }

    @Override
    protected void handle(HttpServletRequest request, HttpServletResponse response)
            throws RenderException, IOException, RepositoryException, JahiaForbiddenAccessException {

        checkUserLoggedIn();
        checkUserAuthorized();

        URLResolver urlResolver = urlResolverFactory.createURLResolver(request.getPathInfo(),
                request.getServerName(), request);
        try {
            Query query = getQuery(request, response, urlResolver.getWorkspace(), urlResolver.getLocale());
            if (query == null) {
                return;
            }
            if (logger.isDebugEnabled()) {
                logger.debug("Executing " + query.getLanguage() + " for workspace '" + urlResolver.getWorkspace()
                        + "' and locale '" + urlResolver.getLocale() + "'. Statement: " + query.getStatement());
            }
            writeResults(query.execute(), request, response, query.getLanguage());
        } catch (IllegalArgumentException e) {
            logger.error("Invalid argument", e);
            response.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
        } catch (InvalidQueryException e) {
            logger.error("Invalid query", e);
            response.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
        }
    }

    private JSONObject serializeNode(Node currentNode, int depthLimit, boolean escapeColon,
            Pattern propertyMatchRegexp, Map<String, String> alreadyIncludedPropertyValues)
            throws RepositoryException, JSONException {
        if (skipNode(currentNode)) {
            return null;
        }
        final PropertyIterator stringMap = currentNode.getProperties();
        JSONObject jsonObject = new JSONObject();
        // Map<String,Object> map = new HashMap<String, Object>();
        Set<String> matchingProperties = new HashSet<String>();
        while (stringMap.hasNext()) {
            JCRPropertyWrapper propertyWrapper = (JCRPropertyWrapper) stringMap.next();
            if (skipProperty(propertyWrapper)) {
                continue;
            }
            final int type = propertyWrapper.getType();
            final String name = escapeColon ? JCRContentUtils.replaceColon(propertyWrapper.getName())
                    : propertyWrapper.getName();
            if (type == PropertyType.BINARY) {
                continue;
            }
            if (type == PropertyType.WEAKREFERENCE || type == PropertyType.REFERENCE) {
                if (!propertyWrapper.isMultiple()) {
                    try {
                        jsonObject.put(name, ((JCRNodeWrapper) propertyWrapper.getNode()).getUrl());
                    } catch (ItemNotFoundException ex) {
                        logger.warn(
                                "Referenced Item cannot be found (To solve it you can run the JCR Integrity Tools):",
                                ex);
                        jsonObject.put(name, propertyWrapper.getValue().getString());
                    }
                }
            } else {
                if (!propertyWrapper.isMultiple()) {
                    jsonObject.put(name, propertyWrapper.getValue().getString());
                    // @todo this code is duplicated for multiple values, we need to clean this up.
                    if (propertyMatchRegexp != null
                            && propertyMatchRegexp.matcher(propertyWrapper.getValue().getString()).matches()) {
                        if (alreadyIncludedPropertyValues != null) {
                            String nodeIdentifier = alreadyIncludedPropertyValues
                                    .get(propertyWrapper.getValue().getString());
                            if (nodeIdentifier != null) {
                                if (!nodeIdentifier.equals(currentNode.getIdentifier())) {
                                    // This property value already exists and comes from another node.
                                    return null;
                                }
                            } else {
                                alreadyIncludedPropertyValues.put(propertyWrapper.getValue().getString(),
                                        currentNode.getIdentifier());
                            }
                        }
                        // property starts with the propertyMatchRegexp, let's add it to the list of matching properties.
                        matchingProperties.add(name);
                    }
                } else {
                    JSONArray jsonArray = new JSONArray();
                    Value[] propValues = propertyWrapper.getValues();
                    for (Value propValue : propValues) {
                        jsonArray.put(propValue.getString());
                        if (propertyMatchRegexp != null
                                && propertyMatchRegexp.matcher(propValue.getString()).matches()) {
                            if (alreadyIncludedPropertyValues != null) {
                                String nodeIdentifier = alreadyIncludedPropertyValues.get(propValue.getString());
                                if (nodeIdentifier != null) {
                                    if (!nodeIdentifier.equals(currentNode.getIdentifier())) {
                                        // This property value already exists and comes from another node.
                                        return null;
                                    }
                                } else {
                                    alreadyIncludedPropertyValues.put(propValue.getString(),
                                            currentNode.getIdentifier());
                                }
                            }
                            // property starts with the propertyMatchRegexp, let's add it to the list of matching properties.
                            matchingProperties.add(name);
                        }
                    }
                    jsonObject.put(name, jsonArray);
                }
            }
        }
        // now let's output some node information.
        jsonObject.put("path", currentNode.getPath());
        jsonObject.put("identifier", currentNode.getIdentifier());
        jsonObject.put("index", currentNode.getIndex());
        jsonObject.put("depth", currentNode.getDepth());
        jsonObject.put("nodename", currentNode.getName());
        jsonObject.put("primaryNodeType", currentNode.getPrimaryNodeType().getName());
        if (propertyMatchRegexp != null) {
            jsonObject.put("matchingProperties", new JSONArray(matchingProperties));
        }

        // now let's output the children until we reach the depth limit.
        if ((depthLimit - 1) > 0) {
            final NodeIterator childNodeIterator = currentNode.getNodes();
            JSONArray childMapList = new JSONArray();
            while (childNodeIterator.hasNext()) {
                Node currentChildNode = childNodeIterator.nextNode();
                JSONObject childSerializedMap = serializeNode(currentChildNode, depthLimit - 1, escapeColon,
                        propertyMatchRegexp, alreadyIncludedPropertyValues);
                if (childSerializedMap != null) {
                    childMapList.put(childSerializedMap);
                }
            }
            jsonObject.put("childNodes", childMapList);
        }
        return jsonObject;
    }

    private JSONObject serializeRow(Row row, String[] columns, int depthLimit, boolean escapeColon,
            Set<String> alreadyIncludedIdentifiers, Pattern propertyMatchRegexp,
            Map<String, String> alreadyIncludedPropertyValues) throws RepositoryException, JSONException {
        Node currentNode = row.getNode();

        if (currentNode != null && skipNode(currentNode)) {
            return null;
        }

        JSONObject jsonObject = new JSONObject();

        if (currentNode != null) {
            if (currentNode.isNodeType(Constants.JAHIANT_TRANSLATION)) {
                try {
                    currentNode = currentNode.getParent();
                    if (alreadyIncludedIdentifiers.contains(currentNode.getIdentifier())) {
                        // avoid duplicates due to j:translation nodes.
                        return null;
                    }
                    JSONObject serializedNode = serializeNode(currentNode, depthLimit, escapeColon,
                            propertyMatchRegexp, alreadyIncludedPropertyValues);
                    if (serializedNode == null) {
                        return null;
                    }
                    jsonObject.put("node", serializedNode);
                    alreadyIncludedIdentifiers.add(currentNode.getIdentifier());
                } catch (ItemNotFoundException e) {
                    currentNode = null;
                }
            } else {
                if (alreadyIncludedIdentifiers.contains(currentNode.getIdentifier())) {
                    // avoid duplicates due to j:translation nodes.
                    return null;
                }
                JSONObject serializedNode = serializeNode(currentNode, depthLimit, escapeColon, propertyMatchRegexp,
                        alreadyIncludedPropertyValues);
                if (serializedNode == null) {
                    return null;
                }
                jsonObject.put("node", serializedNode);
                alreadyIncludedIdentifiers.add(currentNode.getIdentifier());
            }

        }

        for (String column : columns) {
            if (currentNode != null) {
                if (!"jcr:score".equals(column) && !"jcr:path".equals(column) && !column.startsWith("rep:")
                        && !currentNode.hasProperty(
                                column.contains(".") ? StringUtils.substringAfter(column, ".") : column)) {
                    continue;
                }
            }
            try {
                if (skipRowColumn(column)) {
                    continue;
                }
                Value value = row.getValue(column);
                jsonObject.put(escapeColon ? JCRContentUtils.replaceColon(column) : column,
                        value != null ? value.getString() : null);
            } catch (ItemNotFoundException infe) {
                logger.warn("No value found for column " + column);
            } catch (PathNotFoundException pnfe) {
                logger.warn("No value found for column " + column);
            }
        }

        return jsonObject;
    }

    /**
     * @param defaultDepthLimit the defaultDepthLimit to set
     */
    public void setDefaultDepthLimit(int defaultDepthLimit) {
        this.defaultDepthLimit = defaultDepthLimit;
    }

    /**
     * @param defaultEscapeColon the defaultEscapeColon to set
     */
    public void setDefaultEscapeColon(boolean defaultEscapeColon) {
        this.defaultEscapeColon = defaultEscapeColon;
    }

    public boolean isDefaultRemoveDuplicatePropertyValues() {
        return defaultRemoveDuplicatePropertyValues;
    }

    public void setDefaultRemoveDuplicatePropertyValues(boolean defaultRemoveDuplicatePropertyValues) {
        this.defaultRemoveDuplicatePropertyValues = defaultRemoveDuplicatePropertyValues;
    }

    private void writeResults(QueryResult result, HttpServletRequest request, HttpServletResponse response,
            String queryLanguage)
            throws RepositoryException, IllegalArgumentException, IOException, RenderException {
        response.setContentType("application/json; charset=UTF-8");
        int depth = getInt("depthLimit", defaultDepthLimit, request);
        boolean escape = Boolean.valueOf(StringUtils.defaultIfEmpty(request.getParameter("escapeColon"),
                String.valueOf(defaultEscapeColon)));
        boolean removeDuplicatePropertyValues = Boolean
                .valueOf(StringUtils.defaultIfEmpty(request.getParameter("removeDuplicatePropValues"),
                        String.valueOf(defaultRemoveDuplicatePropertyValues)));

        Pattern propertyMatchRegexp = null;
        String propertyMatchRegexpString = request.getParameter("propertyMatchRegexp");
        if (propertyMatchRegexpString != null) {
            String expandedPattern = expandRequestMarkers(request, propertyMatchRegexpString, false, queryLanguage,
                    true);
            propertyMatchRegexp = Pattern.compile(expandedPattern, Pattern.CASE_INSENSITIVE);
        }

        JSONArray results = new JSONArray();

        try {
            String[] columns = result.getColumnNames();
            boolean serializeRows = !Boolean.parseBoolean(request.getParameter("getNodes")) && columns.length > 0
                    && !columns[0].contains("*");

            Set<String> alreadyIncludedIdentifiers = new HashSet<String>();
            Map<String, String> alreadyIncludedPropertyValues = null;
            if (removeDuplicatePropertyValues) {
                alreadyIncludedPropertyValues = new HashMap<String, String>();
            }
            int resultCount = 0;
            if (serializeRows) {
                logger.debug("Serializing rows into JSON result structure...");
                RowIterator rows = result.getRows();
                while (rows.hasNext()) {
                    Row row = rows.nextRow();
                    JSONObject serializedRow = serializeRow(row, columns, depth, escape, alreadyIncludedIdentifiers,
                            propertyMatchRegexp, alreadyIncludedPropertyValues);
                    if (serializedRow != null) {
                        results.put(serializedRow);
                        resultCount++;
                    }
                }
            } else {
                logger.debug("Serializing nodes into JSON result structure...");
                NodeIterator nodes = result.getNodes();
                while (nodes.hasNext()) {
                    Node nextNode = nodes.nextNode();
                    JSONObject serializedNode = serializeNode(nextNode, depth, escape, propertyMatchRegexp,
                            alreadyIncludedPropertyValues);
                    if (serializedNode != null) {
                        results.put(serializedNode);
                        resultCount++;
                    }
                }
            }
            logger.debug("Found {} results.", resultCount);
            results.write(response.getWriter());
        } catch (JSONException e) {
            throw new RenderException(e);
        }
    }

    public static String getFindServletPath() {
        // TODO move this into configuration
        return "/cms/find";
    }

    public void setNodeTypesToSkip(String nodeTypesToSkip) {
        this.nodeTypesToSkip = toSet(nodeTypesToSkip);
    }

    public void setPathsToSkip(String pathsToSkip) {
        this.pathsToSkip = toSet(pathsToSkip);
    }

    public void setPropertiesToSkip(String propertiesToSkipString) {
        propertiesToSkip = toSet(propertiesToSkipString);
        if (propertiesToSkip == null) {
            propertiesToSkipByNodeType = null;
        } else {
            propertiesToSkipByNodeType = new HashMap<String, Set<String>>();
            for (String p : propertiesToSkip) {
                String ntName = "*";
                String propName = p;
                int pos = p.indexOf('.');
                if (pos != -1) {
                    ntName = p.substring(0, pos);
                    propName = p.substring(pos + 1, p.length());
                } else {
                    propertiesToSkip.add("*." + p);
                }
                Set<String> ntProps = propertiesToSkipByNodeType.get(ntName);
                if (ntProps == null) {
                    ntProps = new HashSet<String>();
                    propertiesToSkipByNodeType.put(ntName, ntProps);
                }
                ntProps.add(propName);
            }
        }
    }

    private boolean skipNode(Node currentNode) throws RepositoryException {
        if (pathsToSkip != null) {
            String path = currentNode.getPath();
            if (pathsToSkip.contains(path)) {
                return true;
            }
        }
        if (nodeTypesToSkip != null) {
            String primary = currentNode.getPrimaryNodeType().getName();
            if (nodeTypesToSkip.contains(primary)) {
                return true;
            }
            for (String nt : nodeTypesToSkip) {
                if (currentNode.isNodeType(nt)) {
                    return true;
                }
            }
        }

        return false;
    }

    private boolean skipProperty(JCRPropertyWrapper property) throws RepositoryException {
        if (propertiesToSkipByNodeType != null) {
            String nt = property.getDefinition().getDeclaringNodeType().getName();
            Set<String> props = propertiesToSkipByNodeType.get(nt);
            String propName = property.getName();
            boolean skip = props != null && props != null && props.contains(propName);
            if (!skip) {
                skip = props != null && props.contains(propName);
            }
            return skip;
        }

        return false;
    }

    private boolean skipRowColumn(String column) throws RepositoryException {
        return propertiesToSkip != null && propertiesToSkip.contains(column);
    }

}