001/*
002 *  Copyright 2015 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.cms.search.query;
017
018import java.lang.reflect.Modifier;
019import java.util.Arrays;
020import java.util.HashMap;
021import java.util.Map;
022import java.util.stream.Collectors;
023import java.util.stream.Stream;
024
025import org.apache.commons.lang3.StringUtils;
026
027/**
028 * Represents a search predicate.
029 */
030@FunctionalInterface
031public interface Query
032{
033    
034    /** Enumeration of available operators in {@link Query} */
035    public enum Operator 
036    {
037        /** Operator testing the existence of a property. */
038        EXISTS("exists"),
039        /** Constant of test's operator for textual search, on unstemmed terms. Works only for type String */
040        SEARCH("search"),
041        /** Constant of test's operator for textual search, on stemmed terms. Works only for type String */
042        SEARCH_STEMMED("searchStemmed"),
043        /** Constant of test's operator for 'like' comparison. Works only for type String */
044        LIKE("like"),
045        /** Constant of test's operator for 'less than' comparison */
046        LT("lt"),
047        /** Constant of test's operator for 'less than or equals to' comparison */
048        LE("le"),
049        /** Constant of test's operator for 'greater than' comparison */
050        GT("gt"),
051        /** Constant of test's operator for 'greater than or equals to' comparison */
052        GE("ge"),
053        /** Constant of test's operator for 'equals to' comparison */
054        EQ("eq"),
055        /** Constant of test's operator for 'not equals to' comparison */
056        NE("ne");
057        
058        private static Map<String, Operator> _OP_NAMES = new HashMap<>();
059        static 
060        {
061            for (Operator op : values())
062            {
063                _OP_NAMES.put(op.getName(), op);
064            }
065        }
066        
067        private final String _name;
068        
069        Operator(String name)
070        {
071            _name = name;
072        }
073        
074        /**
075         * Get the operator name.
076         * @return the operator name.
077         */
078        public String getName()
079        {
080            return _name;
081        }
082        
083        /**
084         * Get an Operator object from its name.
085         * @param name the operator name.
086         * @return the operator object, or null if not found.
087         */
088        public static Operator fromName(String name)
089        {
090            if (_OP_NAMES.containsKey(name))
091            {
092                return _OP_NAMES.get(name);
093            }
094            throw new IllegalArgumentException("No operator with code '" + name + "'");
095        }
096    }
097    
098    /** Enumeration of available logical operators in {@link Query} */
099    public enum LogicalOperator 
100    {
101        /** Logical operator AND */
102        AND,
103        /** Logical operator OR */
104        OR
105    }
106    
107    /**
108     * Build the solr query string representing the Query object.
109     * @return the solr query string representing the Query object.
110     * @throws QuerySyntaxException if the query can't be built because of a syntax error.
111     */
112    String build() throws QuerySyntaxException;
113    
114    /**
115     * Gets a representation of this {@link Query}, for pretty-printing for logging and debugging purposes
116     * @param indent The current indentation. Base indentation is 2 (for printing a sub-level)
117     * @return a representation of this {@link Query}
118     */
119    default String toString(int indent)
120    {
121        String toStr;
122        try
123        {
124            toStr = this.getClass().getSimpleName()
125                + "("
126                + Stream.of(this.getClass().getDeclaredFields())
127                    .filter(f -> !Modifier.isStatic(f.getModifiers()))
128                    .map(f ->
129                    {
130                        try
131                        {
132                            f.setAccessible(true);
133                            Object val = f.get(this);
134                            String strVal = val != null && val.getClass().isArray() ? Arrays.toString((Object[]) val) : String.valueOf(val);
135                            return f.getName() + ":" + strVal;
136                        }
137                        catch (Exception e)
138                        {
139                            return "";
140                        }
141                    })
142                    .collect(Collectors.joining(","))
143                + ")";
144        }
145        catch (Exception e)
146        {
147            toStr = toString();
148        }
149        return StringUtils.repeat(' ', indent) + toStr;
150    }
151}