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

io.uhndata.cards.spi.SearchUtils Maven / Gradle / Ivy

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package io.uhndata.cards.spi;

import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;

import org.apache.commons.lang3.StringUtils;
import org.apache.jackrabbit.spi.commons.conversion.IllegalNameException;
import org.apache.jackrabbit.spi.commons.conversion.NameParser;

/**
 * Service interface used by {@link io.uhndata.cards.QueryBuilder} to search for a specific type of resource.
 *
 * @version $Id: e9264999da8fac3ed674f684ecc76f2f5c2e70c6 $
 */
public final class SearchUtils
{
    private static final int MAX_CONTEXT_MATCH = 8;

    // Property of the parent node in an quick search, outlining what needs to be highlighted
    private static final String CARDS_QUERY_MATCH_KEY = "cards:queryMatch";

    // Properties of the children nodes
    private static final String CARDS_QUERY_QUESTION_KEY = "question";

    private static final String CARDS_QUERY_MATCH_BEFORE_KEY = "before";

    private static final String CARDS_QUERY_MATCH_TEXT_KEY = "text";

    private static final String CARDS_QUERY_MATCH_AFTER_KEY = "after";

    private static final String CARDS_QUERY_MATCH_NOTES_KEY = "inNotes";

    private static final String CARDS_QUERY_MATCH_PATH_KEY = "@path";

    private SearchUtils()
    {
        // This is a utility class, it should not be instantiated
    }

    /**
     * Escapes the input string to be free of characters with special meaning in a {@code jcr:like} query.
     *
     * @param input text to escape
     * @return an escaped version of the input
     */
    public static String escapeLikeText(final String input)
    {
        return input.replaceAll("([\\\\%_'])", "\\\\$1");
    }

    /**
     * Escapes the input string to be usable in a string argument.
     *
     * @param input text to escape
     * @return an escaped version of the input
     */
    public static String escapeQueryArgument(final String input)
    {
        return input.replace("'", "''");
    }

    /**
     * Searches through a list of Strings and returns the first String in that list for which in itself contains a given
     * substring.
     *
     * @param value the raw answer value, may be a single or multivalue of any JCR type
     * @param str the String to check if the value contains this substring
     * @return the (single) value, or the first value in a multivalue that contains the query substring
     */
    public static String getMatch(Object value, String str)
    {
        if (value == null) {
            return null;
        }

        if (value instanceof String[]) {
            return getMatchFromArray((String[]) value, str);
        } else if (value instanceof Object[]) {
            Object[] valueArray = (Object[]) value;
            String[] valueStr = new String[valueArray.length];
            for (int i = 0; i < valueArray.length; ++i) {
                valueStr[i] = String.valueOf(valueArray[i]);
            }
            return getMatchFromArray(valueStr, str);
        } else if (value != null) {
            if (StringUtils.containsIgnoreCase(value.toString(), str)) {
                return value.toString();
            }
        }

        return null;
    }

    /**
     * Searches through a list of Strings and returns the first String in that list for which in itself contains a given
     * substring.
     *
     * @param arr the list of Strings to search through
     * @param str the String to check if any array elements contain this substring
     * @return the first String in the list that contains the given substring
     */
    public static String getMatchFromArray(String[] arr, String str)
    {
        if (arr == null) {
            return null;
        }

        for (String element : arr) {
            if (StringUtils.containsIgnoreCase(element, str)) {
                return element;
            }
        }
        return null;
    }

    /**
     * Add metadata about a match to the matching object's parent.
     *
     * @param resourceValue The value that was matched
     * @param query The search value
     * @param question The text of the question itself
     * @param parent The parent of the matching node
     * @param isNoteMatch Whether or not the match is on the notes of the answer, rather than the answer
     * @param path the matching answer question node path
     * @return The given JsonObject with metadata appended to it.
     */
    public static JsonObject addMatchMetadata(String resourceValue, String query, String question, JsonObject parent,
        boolean isNoteMatch, String path)
    {
        JsonObject metadata = getMatchMetadata(resourceValue, query, question, isNoteMatch, path);

        // Construct a JsonObject that matches the parent, but with custom match metadata appended
        JsonObjectBuilder builder = Json.createObjectBuilder();
        for (String key : parent.keySet()) {
            builder.add(key, parent.get(key));
        }
        builder.add(CARDS_QUERY_MATCH_KEY, metadata);
        return builder.build();
    }

    /**
     * Get the metadata about a match.
     *
     * @param resourceValue The value that was matched
     * @param query The search value
     * @param question The text of the question itself
     * @param isNoteMatch Whether or not the match is on the notes of the answer, rather than the answer
     * @param path the matching answer question node path
     * @return the metadata as a JsonObject
     */
    private static JsonObject getMatchMetadata(String resourceValue, String query, String question, boolean isNoteMatch,
        String path)
    {
        JsonObjectBuilder builder = Json.createObjectBuilder();
        builder.add(CARDS_QUERY_QUESTION_KEY, question);
        builder.add(CARDS_QUERY_MATCH_NOTES_KEY, isNoteMatch);
        builder.add(CARDS_QUERY_MATCH_PATH_KEY, path);

        // Add metadata about the text before the match
        int matchIndex = resourceValue.toLowerCase().indexOf(query.toLowerCase());
        String matchBefore = resourceValue.substring(0, matchIndex);
        if (matchBefore.length() > MAX_CONTEXT_MATCH) {
            matchBefore = "..." + matchBefore.substring(
                matchBefore.length() - MAX_CONTEXT_MATCH, matchBefore.length());
        }
        builder.add(CARDS_QUERY_MATCH_BEFORE_KEY, matchBefore);

        // Add metadata about the text matched
        String matchText = resourceValue.substring(matchIndex, matchIndex + query.length());
        builder.add(CARDS_QUERY_MATCH_TEXT_KEY, matchText);

        // Add metadata about the text after the match
        String matchAfter = resourceValue.substring(matchIndex + query.length());
        if (matchAfter.length() > MAX_CONTEXT_MATCH) {
            matchAfter = matchAfter.substring(0, MAX_CONTEXT_MATCH) + "...";
        }
        builder.add(CARDS_QUERY_MATCH_AFTER_KEY, matchAfter);

        return builder.build();
    }

    /**
     * Check whether the given name is a valid node name.
     *
     * @param name Node name to check
     * @return True if the given name is a valid node name
     */
    public static boolean isValidNodeName(String name)
    {
        try {
            NameParser.checkFormat(name);
            return true;
        } catch (IllegalNameException e) {
            return false;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy