OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 80 | Rev 93 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
17 ilm 1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
7
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
8
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
9
 * language governing permissions and limitations under the License.
10
 *
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
13
 
14
 package org.openconcerto.sql.request;
15
 
16
import org.openconcerto.sql.FieldExpander;
83 ilm 17
import org.openconcerto.sql.model.FieldRef;
17 ilm 18
import org.openconcerto.sql.model.SQLField;
19
import org.openconcerto.sql.model.SQLRowValues;
20
import org.openconcerto.sql.model.SQLRowValuesListFetcher;
83 ilm 21
import org.openconcerto.sql.model.SQLSearchMode;
17 ilm 22
import org.openconcerto.sql.model.SQLSelect;
23
import org.openconcerto.sql.model.SQLTable;
24
import org.openconcerto.sql.model.Where;
25
import org.openconcerto.sql.model.graph.Path;
83 ilm 26
import org.openconcerto.utils.CollectionUtils;
17 ilm 27
import org.openconcerto.utils.cc.ITransformer;
28
 
29
import java.beans.PropertyChangeListener;
30
import java.beans.PropertyChangeSupport;
83 ilm 31
import java.util.Arrays;
17 ilm 32
import java.util.Collection;
33
import java.util.Collections;
83 ilm 34
import java.util.HashMap;
35
import java.util.HashSet;
36
import java.util.Iterator;
17 ilm 37
import java.util.List;
83 ilm 38
import java.util.Map;
39
import java.util.Map.Entry;
40
import java.util.Set;
41
import java.util.regex.Pattern;
17 ilm 42
 
83 ilm 43
import net.jcip.annotations.GuardedBy;
44
 
17 ilm 45
public abstract class BaseFillSQLRequest extends BaseSQLRequest {
46
 
83 ilm 47
    private final static Pattern QUERY_SPLIT_PATTERN = Pattern.compile("\\s+");
73 ilm 48
    private static boolean DEFAULT_SELECT_LOCK = true;
49
 
17 ilm 50
    /**
51
     * Whether to use "FOR SHARE" in list requests (preventing roles with just SELECT right from
52
     * seeing the list).
53
     */
73 ilm 54
    public static final boolean getDefaultLockSelect() {
55
        return DEFAULT_SELECT_LOCK;
56
    }
17 ilm 57
 
73 ilm 58
    public static final void setDefaultLockSelect(final boolean b) {
59
        DEFAULT_SELECT_LOCK = b;
60
    }
61
 
20 ilm 62
    static public void setupForeign(final SQLRowValuesListFetcher fetcher) {
63
        // include rows having NULL (not undefined ID) foreign keys
64
        fetcher.setFullOnly(false);
65
        // treat the same way tables with or without undefined ID
66
        fetcher.setIncludeForeignUndef(false);
73 ilm 67
        // be predictable
68
        fetcher.setReferentsOrdered(true, true);
20 ilm 69
    }
70
 
17 ilm 71
    private final SQLTable primaryTable;
72
    private Where where;
83 ilm 73
    @GuardedBy("this")
74
    private Map<SQLField, SQLSearchMode> searchFields;
75
    @GuardedBy("this")
76
    private List<String> searchQuery;
17 ilm 77
    private ITransformer<SQLSelect, SQLSelect> selTransf;
73 ilm 78
    private boolean lockSelect;
17 ilm 79
 
80
    private SQLRowValues graph;
81
    private SQLRowValues graphToFetch;
82
 
83
    private final PropertyChangeSupport supp = new PropertyChangeSupport(this);
84
 
61 ilm 85
    public BaseFillSQLRequest(final SQLTable primaryTable, final Where w) {
17 ilm 86
        super();
87
        if (primaryTable == null)
88
            throw new NullPointerException();
89
        this.primaryTable = primaryTable;
90
        this.where = w;
83 ilm 91
        this.searchFields = Collections.emptyMap();
92
        this.searchQuery = Collections.emptyList();
17 ilm 93
        this.selTransf = null;
73 ilm 94
        this.lockSelect = getDefaultLockSelect();
17 ilm 95
        this.graph = null;
96
        this.graphToFetch = null;
97
    }
98
 
61 ilm 99
    public BaseFillSQLRequest(final BaseFillSQLRequest req) {
17 ilm 100
        super();
101
        this.primaryTable = req.getPrimaryTable();
102
        this.where = req.where;
83 ilm 103
        this.searchFields = req.searchFields;
104
        this.searchQuery = req.searchQuery;
17 ilm 105
        this.selTransf = req.selTransf;
73 ilm 106
        this.lockSelect = req.lockSelect;
17 ilm 107
        // TODO copy
108
        // use methods since they're both lazy
109
        this.graph = req.getGraph();
110
        this.graphToFetch = req.getGraphToFetch();
111
    }
112
 
113
    private final SQLRowValues computeGraph() {
114
        if (this.getFields() == null)
115
            return null;
116
 
117
        final SQLRowValues vals = new SQLRowValues(this.getPrimaryTable());
118
        for (final SQLField f : this.getFields()) {
119
            vals.put(f.getName(), null);
120
        }
121
        // keep order field in graph (not only in graphToFetch) so that a debug column is created
122
        for (final Path orderP : this.getOrder()) {
123
            final SQLRowValues orderVals = vals.followPath(orderP);
124
            if (orderVals != null && orderVals.getTable().isOrdered()) {
125
                orderVals.put(orderVals.getTable().getOrderField().getName(), null);
126
            }
127
        }
128
        this.getShowAs().expand(vals);
129
        return vals;
130
    }
131
 
132
    /**
133
     * The graph computed by expanding {@link #getFields()} by {@link #getShowAs()}.
134
     *
135
     * @return the expanded graph.
136
     */
137
    public final SQLRowValues getGraph() {
138
        if (this.graph == null)
139
            this.graph = this.computeGraph();
140
        return this.graph;
141
    }
142
 
65 ilm 143
    // should be called if getFields(), getOrder() or getShowAs() change
144
    protected final void clearGraph() {
145
        this.graph = null;
146
        this.graphToFetch = null;
147
    }
148
 
17 ilm 149
    /**
150
     * The graph to fetch, should be a superset of {@link #getGraph()}.
151
     *
61 ilm 152
     * @return the graph to fetch, can be modified.
17 ilm 153
     */
154
    public final SQLRowValues getGraphToFetch() {
155
        if (this.graphToFetch == null && this.getGraph() != null) {
156
            this.graphToFetch = this.getGraph().deepCopy();
157
            this.customizeToFetch(this.graphToFetch);
158
        }
159
        return this.graphToFetch;
160
    }
161
 
61 ilm 162
    protected void customizeToFetch(final SQLRowValues graphToFetch) {
17 ilm 163
    }
164
 
165
    protected final SQLRowValuesListFetcher getFetcher(final Where w) {
61 ilm 166
        // graphToFetch can be modified freely so don't the use the simple constructor
65 ilm 167
        final SQLRowValuesListFetcher fetcher = SQLRowValuesListFetcher.create(getGraphToFetch(), false);
67 ilm 168
        return setupFetcher(fetcher, w);
169
    }
170
 
171
    // allow to pass fetcher since they are mostly immutable (and for huge graphs they are slow to
172
    // create)
173
    protected final SQLRowValuesListFetcher setupFetcher(final SQLRowValuesListFetcher fetcher, final Where w) {
174
        final String tableName = getPrimaryTable().getName();
20 ilm 175
        setupForeign(fetcher);
73 ilm 176
        fetcher.setOrder(getOrder());
67 ilm 177
        final ITransformer<SQLSelect, SQLSelect> origSelTransf = fetcher.getSelTransf();
17 ilm 178
        fetcher.setSelTransf(new ITransformer<SQLSelect, SQLSelect>() {
179
            @Override
180
            public SQLSelect transformChecked(SQLSelect sel) {
67 ilm 181
                if (origSelTransf != null)
182
                    sel = origSelTransf.transformChecked(sel);
17 ilm 183
                sel = transformSelect(sel);
73 ilm 184
                if (isLockSelect())
17 ilm 185
                    sel.addWaitPreviousWriteTXTable(tableName);
186
                return sel.andWhere(getWhere()).andWhere(w);
187
            }
188
        });
189
        return fetcher;
190
    }
191
 
192
    protected List<Path> getOrder() {
80 ilm 193
        return Collections.singletonList(Path.get(getPrimaryTable()));
17 ilm 194
    }
195
 
61 ilm 196
    public final void setWhere(final Where w) {
17 ilm 197
        this.where = w;
198
        fireWhereChange();
199
    }
200
 
201
    public final Where getWhere() {
202
        return this.where;
203
    }
204
 
83 ilm 205
    /**
206
     * Whether this request is searchable.
207
     *
208
     * @param b <code>true</code> if the {@link #getFields() local fields} should be used,
209
     *        <code>false</code> to not be searchable.
210
     */
211
    public final void setSearchable(final boolean b) {
212
        this.setSearchFields(b ? this.getFields() : Collections.<SQLField> emptyList());
213
    }
214
 
215
    /**
216
     * Set the fields used to search.
217
     *
218
     * @param searchFields only rows with these fields containing the terms will match.
219
     * @see #setSearch(String)
220
     */
221
    public final void setSearchFields(final Collection<SQLField> searchFields) {
222
        this.setSearchFields(CollectionUtils.<SQLField, SQLSearchMode> createMap(searchFields));
223
    }
224
 
225
    /**
226
     * Set the fields used to search.
227
     *
228
     * @param searchFields for each field to search, how to match.
229
     * @see #setSearch(String)
230
     */
231
    public final void setSearchFields(Map<SQLField, SQLSearchMode> searchFields) {
232
        searchFields = new HashMap<SQLField, SQLSearchMode>(searchFields);
233
        final Iterator<Entry<SQLField, SQLSearchMode>> iter = searchFields.entrySet().iterator();
234
        while (iter.hasNext()) {
235
            final Entry<SQLField, SQLSearchMode> e = iter.next();
236
            if (!String.class.isAssignableFrom(e.getKey().getType().getJavaType())) {
237
                iter.remove();
238
            } else if (e.getValue() == null) {
239
                e.setValue(SQLSearchMode.CONTAINS);
240
            }
241
        }
242
        searchFields = Collections.unmodifiableMap(searchFields);
243
        synchronized (this) {
244
            this.searchFields = searchFields;
245
        }
246
        fireWhereChange();
247
    }
248
 
249
    public Map<SQLField, SQLSearchMode> getSearchFields() {
250
        synchronized (this) {
251
            return this.searchFields;
252
        }
253
    }
254
 
255
    /**
256
     * Set the search query. The query will be used to match rows using
257
     * {@link #setSearchFields(Map)}. I.e. if there's no field set, this method won't have any
258
     * effect.
259
     *
260
     * @param s the search query.
261
     * @return <code>true</code> if the request changed.
262
     */
263
    public boolean setSearch(String s) {
264
        // no need to trim() since trailing empty strings are not returned
265
        final List<String> split = Arrays.asList(QUERY_SPLIT_PATTERN.split(s));
266
        synchronized (this) {
267
            if (!split.equals(this.searchQuery)) {
268
                this.searchQuery = split;
269
                if (!this.getSearchFields().isEmpty()) {
270
                    this.fireWhereChange();
271
                    return true;
272
                }
273
            }
274
            return false;
275
        }
276
    }
277
 
73 ilm 278
    public final void setLockSelect(boolean lockSelect) {
279
        this.lockSelect = lockSelect;
280
    }
281
 
282
    public final boolean isLockSelect() {
283
        return this.lockSelect;
284
    }
285
 
61 ilm 286
    @Override
17 ilm 287
    public final Collection<SQLField> getAllFields() {
288
        // don't rely on the expansion of our fields, since our fetcher can be arbitrary modified
289
        // (eg by adding a where on a field of a non-displayed table)
290
        return this.getFetcher(null).getReq().getFields();
291
    }
292
 
293
    protected abstract Collection<SQLField> getFields();
294
 
61 ilm 295
    protected SQLSelect transformSelect(final SQLSelect sel) {
83 ilm 296
        final Map<SQLField, SQLSearchMode> searchFields;
297
        final List<String> searchQuery;
298
        synchronized (this) {
299
            searchFields = this.getSearchFields();
300
            searchQuery = this.searchQuery;
301
        }
302
        final Where w;
303
        final Set<String> matchScore = new HashSet<String>();
304
        if (!searchFields.isEmpty()) {
305
            Where where = null;
306
            for (final String searchTerm : searchQuery) {
307
                Where termWhere = null;
308
                for (final FieldRef selF : sel.getSelectFields()) {
309
                    final SQLSearchMode mode = searchFields.get(selF.getField());
310
                    if (mode != null) {
311
                        termWhere = Where.createRaw(createWhere(selF, mode, searchTerm)).or(termWhere);
312
                        if (!mode.equals(SQLSearchMode.EQUALS))
313
                            matchScore.add("case when " + createWhere(selF, SQLSearchMode.EQUALS, searchTerm) + " then 1 else 0 end");
314
                    }
315
                }
316
                where = Where.and(termWhere, where);
317
            }
318
            w = where;
319
        } else {
320
            w = null;
321
        }
322
        sel.andWhere(w);
323
        if (!matchScore.isEmpty())
324
            sel.getOrder().add(0, CollectionUtils.join(matchScore, " + ") + " DESC");
325
 
17 ilm 326
        return this.selTransf == null ? sel : this.selTransf.transformChecked(sel);
327
    }
328
 
83 ilm 329
    protected String createWhere(final FieldRef selF, final SQLSearchMode mode, final String searchQuery) {
330
        return "lower(" + selF.getFieldRef() + ") " + mode.generateSQL(selF.getField().getDBRoot(), searchQuery.toLowerCase());
331
    }
332
 
17 ilm 333
    public final ITransformer<SQLSelect, SQLSelect> getSelectTransf() {
334
        return this.selTransf;
335
    }
336
 
337
    /**
338
     * Allows to transform the SQLSelect returned by getFillRequest().
339
     *
340
     * @param transf the transformer to apply.
341
     */
61 ilm 342
    public final void setSelectTransf(final ITransformer<SQLSelect, SQLSelect> transf) {
17 ilm 343
        this.selTransf = transf;
344
        this.fireWhereChange();
345
    }
346
 
347
    protected abstract FieldExpander getShowAs();
348
 
349
    public final SQLTable getPrimaryTable() {
350
        return this.primaryTable;
351
    }
352
 
353
    protected final void fireWhereChange() {
354
        this.supp.firePropertyChange("where", null, null);
355
    }
356
 
61 ilm 357
    public final void addWhereListener(final PropertyChangeListener l) {
17 ilm 358
        this.supp.addPropertyChangeListener("where", l);
359
    }
360
 
61 ilm 361
    public final void rmWhereListener(final PropertyChangeListener l) {
17 ilm 362
        this.supp.removePropertyChangeListener("where", l);
363
    }
364
 
61 ilm 365
    @Override
17 ilm 366
    public String toString() {
367
        return this.getClass().getName() + " on " + this.getPrimaryTable();
368
    }
369
}