001/*
002 * Stallion Core: A Modern Web Framework
003 *
004 * Copyright (C) 2015 - 2016 Stallion Software LLC.
005 *
006 * This program is free software: you can redistribute it and/or modify it under the terms of the
007 * GNU General Public License as published by the Free Software Foundation, either version 2 of
008 * the License, or (at your option) any later version. This program is distributed in the hope that
009 * it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
010 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
011 * License for more details. You should have received a copy of the GNU General Public License
012 * along with this program.  If not, see <http://www.gnu.org/licenses/gpl-2.0.html>.
013 *
014 *
015 *
016 */
017
018package io.stallion.restfulEndpoints;
019
020import java.util.*;
021
022import static io.stallion.utils.Literals.*;
023
024import io.stallion.dataAccess.Model;
025import io.stallion.dataAccess.ModelController;
026import io.stallion.dataAccess.filtering.*;
027import io.stallion.requests.IRequest;
028import io.stallion.services.Log;
029import io.stallion.utils.json.JSON;
030import org.apache.commons.lang3.StringUtils;
031
032
033public class QueryToPager<T extends Model> {
034    /*
035       .filter()
036    .exclude()
037    .allSortable()
038    .allFilters()
039    .allowedFilters()
040    .allowedSortable()
041    .searchFields()
042    .pageSize()
043    .pager()
044    .chain();
045
046     */
047    private FilterChain<T> chain;
048    private ModelController<T> controller;
049    private IRequest request;
050    private List<String> _allowedFilters = list();
051    private List<String> _allowedSortable = list();
052    private List<String> _searchFields = list();
053    private boolean _allFilters = false;
054    private boolean _allSorts = false;
055    private Integer _pageSize = 50;
056    private boolean _requestProcessed = false;
057    private String defaultSort = "";
058
059
060
061
062
063    public QueryToPager(IRequest request, ModelController<T> controller) {
064        this(request, controller, controller.filterChain());
065    }
066
067    public QueryToPager(IRequest request, ModelController<T> controller, FilterChain<T> chain) {
068        this.request = request;
069        this.controller = controller;
070        this.chain = chain;
071    }
072
073    public QueryToPager<T> filter(String name, Object value, FilterOperator op) {
074        this.chain = chain.filterBy(name, value, op);
075        return this;
076    }
077
078    public QueryToPager<T> exclude(String name, Object value, FilterOperator op) {
079        this.chain = chain.excludeBy(name, value, op);
080        return this;
081    }
082
083    public QueryToPager<T> allowedFilters(String ...fields) {
084        this._allowedFilters = asList(fields);
085        return this;
086    }
087
088    public QueryToPager<T> allowedSortable(String...fields) {
089        this._allowedSortable = asList(fields);
090        return this;
091    }
092
093    public QueryToPager<T> searchFields(String...fields) {
094        this._searchFields = asList(fields);
095        return this;
096    }
097
098
099
100
101
102    public QueryToPager<T> andAnyOf(Or...ors) {
103        this.chain = chain.andAnyOf(ors);
104        return this;
105    }
106
107    public QueryToPager<T> allSortable() {
108        this._allSorts= true;
109        return this;
110    }
111
112    public QueryToPager<T> allFilters() {
113        this._allFilters = true;
114        return this;
115    }
116
117    public QueryToPager<T> pageSize(Integer pageSize) {
118        this._pageSize = pageSize;
119        return this;
120    }
121
122
123    public FilterChain<T> chain() {
124        if (!_requestProcessed) {
125            process();
126            this._requestProcessed = true;
127        }
128        return this.chain;
129    }
130
131    public Pager<T> pager() {
132        if (!_requestProcessed) {
133            process();
134            this._requestProcessed = true;
135        }
136        Integer page = 1;
137        if (!empty(request.getParameter("page"))) {
138            page = Integer.parseInt(request.getParameter("page"));
139        }
140
141        return this.chain.pager(page, _pageSize);
142
143    }
144
145    protected void process() {
146        // ?filters=&search=&page=&sort=&filter_by...
147        Map<String, String> params = this.request.getQueryParams();
148        String search = params.getOrDefault("search", null);
149        if (!empty(search)) {
150            if (!empty(_searchFields)) {
151                this.chain = chain.search(search, asArray(_searchFields, String.class));
152            } else {
153                Log.warn("Search included, but no search fields defined");
154            }
155        }
156        String filters = params.getOrDefault("filters", null);
157        if (!empty(filters)) {
158            List<LinkedHashMap> filterObjects = JSON.parseList(filters);
159            for (LinkedHashMap<String, Object> o: filterObjects) {
160                String field = o.get("name").toString();
161                if (_allFilters && !_allowedFilters.contains(field)) {
162                    Log.warn("Filter not allowed: " + field);
163                    continue;
164                }
165
166                Object value = o.get("value");
167                if (value instanceof Collection) {
168                    value = new ArrayList((Collection) value);
169                }
170                String operation = (String)o.getOrDefault("op", "=");
171                this.chain = chain.filter(field, value, operation);
172            }
173        }
174        String sort = or(params.getOrDefault("sort", null), defaultSort);
175        if (!empty(sort)) {
176            SortDirection dir = SortDirection.ASC;
177            if (sort.startsWith("-")) {
178                sort = sort.substring(1);
179                dir = SortDirection.DESC;
180            }
181            if (_allSorts && !_allowedSortable.contains(sort)) {
182                Log.warn("Sort not allowed: " + sort);
183            } else {
184                this.chain = chain.sortBy(sort, dir);
185            }
186        }
187
188        for(String filter: request.getQueryParamAsList("filter_by")) {
189            if (empty(filter) || !filter.contains(":")) {
190                continue;
191            }
192            String[] parts = StringUtils.split(filter, ":", 3);
193            String key = parts[0];
194            if (_allFilters && !_allowedFilters.contains(key)) {
195                Log.warn("Filter not allowed: " + key);
196                continue;
197            }
198            String val;
199            String op = "eq";
200            if (parts.length > 2) {
201                op = parts[1];
202                val = parts[2];
203            } else {
204                val = parts[1];
205            }
206            this.chain = chain.filter(key, val, op);
207
208        }
209
210
211
212    }
213
214    public QueryToPager setDefaultSort(String defaultSort) {
215        this.defaultSort = defaultSort;
216        return this;
217    }
218}