OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 177 | Only display areas with differences | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 177 Rev 182
1
/*
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 * 
3
 * 
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
4
 * Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
5
 * 
5
 * 
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
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
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
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.
9
 * language governing permissions and limitations under the License.
10
 * 
10
 * 
11
 * When distributing the software, include this License Header Notice in each file.
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
12
 */
13
 
13
 
14
 package org.openconcerto.sql.request;
14
 package org.openconcerto.sql.request;
15
 
15
 
16
import org.openconcerto.sql.FieldExpander;
16
import org.openconcerto.sql.FieldExpander;
17
import org.openconcerto.sql.model.FieldRef;
17
import org.openconcerto.sql.model.FieldRef;
18
import org.openconcerto.sql.model.IFieldPath;
18
import org.openconcerto.sql.model.IFieldPath;
19
import org.openconcerto.sql.model.OrderComparator;
19
import org.openconcerto.sql.model.OrderComparator;
20
import org.openconcerto.sql.model.SQLField;
20
import org.openconcerto.sql.model.SQLField;
-
 
21
import org.openconcerto.sql.model.SQLRow;
21
import org.openconcerto.sql.model.SQLRowAccessor;
22
import org.openconcerto.sql.model.SQLRowAccessor;
22
import org.openconcerto.sql.model.SQLRowValues;
23
import org.openconcerto.sql.model.SQLRowValues;
23
import org.openconcerto.sql.model.SQLRowValues.CreateMode;
24
import org.openconcerto.sql.model.SQLRowValues.CreateMode;
24
import org.openconcerto.sql.model.SQLRowValuesListFetcher;
25
import org.openconcerto.sql.model.SQLRowValuesListFetcher;
25
import org.openconcerto.sql.model.SQLSearchMode;
26
import org.openconcerto.sql.model.SQLSearchMode;
26
import org.openconcerto.sql.model.SQLSelect;
27
import org.openconcerto.sql.model.SQLSelect;
27
import org.openconcerto.sql.model.SQLSyntax;
28
import org.openconcerto.sql.model.SQLSyntax;
28
import org.openconcerto.sql.model.SQLSyntax.CaseBuilder;
29
import org.openconcerto.sql.model.SQLSyntax.CaseBuilder;
29
import org.openconcerto.sql.model.SQLSyntax.DateProp;
30
import org.openconcerto.sql.model.SQLSyntax.DateProp;
30
import org.openconcerto.sql.model.SQLTable;
31
import org.openconcerto.sql.model.SQLTable;
31
import org.openconcerto.sql.model.SQLTable.VirtualFields;
32
import org.openconcerto.sql.model.SQLTable.VirtualFields;
32
import org.openconcerto.sql.model.SQLTableModifiedListener;
33
import org.openconcerto.sql.model.SQLTableModifiedListener;
33
import org.openconcerto.sql.model.SQLType;
34
import org.openconcerto.sql.model.SQLType;
34
import org.openconcerto.sql.model.Where;
35
import org.openconcerto.sql.model.Where;
35
import org.openconcerto.sql.model.graph.Path;
36
import org.openconcerto.sql.model.graph.Path;
36
import org.openconcerto.utils.CollectionUtils;
37
import org.openconcerto.utils.CollectionUtils;
37
import org.openconcerto.utils.Tuple2;
38
import org.openconcerto.utils.Tuple2;
38
import org.openconcerto.utils.cc.IClosure;
39
import org.openconcerto.utils.cc.IClosure;
39
import org.openconcerto.utils.cc.ITransformer;
40
import org.openconcerto.utils.cc.ITransformer;
40
 
41
 
41
import java.beans.PropertyChangeListener;
42
import java.beans.PropertyChangeListener;
42
import java.beans.PropertyChangeSupport;
43
import java.beans.PropertyChangeSupport;
43
import java.sql.Date;
44
import java.sql.Date;
44
import java.sql.Time;
45
import java.sql.Time;
45
import java.sql.Timestamp;
46
import java.sql.Timestamp;
46
import java.util.ArrayList;
47
import java.util.ArrayList;
47
import java.util.Arrays;
48
import java.util.Arrays;
48
import java.util.Collection;
49
import java.util.Collection;
49
import java.util.Collections;
50
import java.util.Collections;
50
import java.util.Comparator;
51
import java.util.Comparator;
51
import java.util.HashMap;
52
import java.util.HashMap;
52
import java.util.HashSet;
53
import java.util.HashSet;
53
import java.util.List;
54
import java.util.List;
54
import java.util.Locale;
55
import java.util.Locale;
55
import java.util.Map;
56
import java.util.Map;
56
import java.util.Map.Entry;
57
import java.util.Map.Entry;
57
import java.util.Set;
58
import java.util.Set;
58
 
59
 
59
import net.jcip.annotations.GuardedBy;
60
import net.jcip.annotations.GuardedBy;
60
import net.jcip.annotations.ThreadSafe;
61
import net.jcip.annotations.ThreadSafe;
61
 
62
 
62
@ThreadSafe
63
@ThreadSafe
63
public abstract class BaseFillSQLRequest extends BaseSQLRequest {
64
public abstract class BaseFillSQLRequest extends BaseSQLRequest {
64
 
65
 
65
    private static boolean DEFAULT_SELECT_LOCK = true;
66
    private static boolean DEFAULT_SELECT_LOCK = true;
66
 
67
 
67
    /**
68
    /**
68
     * Whether to use "FOR SHARE" in list requests (preventing roles with just SELECT right from
69
     * Whether to use "FOR SHARE" in list requests (preventing roles with just SELECT right from
69
     * seeing the list).
70
     * seeing the list).
70
     * 
71
     * 
71
     * @return <code>true</code> if select should obtain a lock.
72
     * @return <code>true</code> if select should obtain a lock.
72
     * @see SQLSelect#setWaitPreviousWriteTX(boolean)
73
     * @see SQLSelect#setWaitPreviousWriteTX(boolean)
73
     */
74
     */
74
    public static final boolean getDefaultLockSelect() {
75
    public static final boolean getDefaultLockSelect() {
75
        return DEFAULT_SELECT_LOCK;
76
        return DEFAULT_SELECT_LOCK;
76
    }
77
    }
77
 
78
 
78
    public static final void setDefaultLockSelect(final boolean b) {
79
    public static final void setDefaultLockSelect(final boolean b) {
79
        DEFAULT_SELECT_LOCK = b;
80
        DEFAULT_SELECT_LOCK = b;
80
    }
81
    }
81
 
82
 
82
    static public void setupForeign(final SQLRowValuesListFetcher fetcher) {
83
    static public void setupForeign(final SQLRowValuesListFetcher fetcher) {
83
        // include rows having NULL (not undefined ID) foreign keys
84
        // include rows having NULL (not undefined ID) foreign keys
84
        fetcher.setFullOnly(false);
85
        fetcher.setFullOnly(false);
85
        // treat the same way tables with or without undefined ID
86
        // treat the same way tables with or without undefined ID
86
        fetcher.setIncludeForeignUndef(false);
87
        fetcher.setIncludeForeignUndef(false);
87
        // be predictable
88
        // be predictable
88
        fetcher.setReferentsOrdered(true, true);
89
        fetcher.setReferentsOrdered(true, true);
89
    }
90
    }
90
 
91
 
91
    static public final boolean addToFetch(final SQLRowValues input, final Path p, final Collection<String> fields) {
92
    static public final boolean addToFetch(final SQLRowValues input, final Path p, final Collection<String> fields) {
92
        assert p == null || p.isSingleLink() : "Graph size not sufficient to know if graph was modified";
93
        assert p == null || p.isSingleLink() : "Graph size not sufficient to know if graph was modified";
93
        final int graphSize = input.getGraphSize();
94
        final int graphSize = input.getGraphSize();
94
        // don't back track : e.g. if path is SITE -> CLIENT <- SITE we want the siblings of SITE,
95
        // don't back track : e.g. if path is SITE -> CLIENT <- SITE we want the siblings of SITE,
95
        // if we want fields of the primary SITE we pass the path SITE
96
        // if we want fields of the primary SITE we pass the path SITE
96
        final SQLRowValues r = p == null ? input : input.followPathToOne(p, CreateMode.CREATE_ONE, false);
97
        final SQLRowValues r = p == null ? input : input.followPathToOne(p, CreateMode.CREATE_ONE, false);
97
        boolean modified = input.getGraphSize() > graphSize;
98
        boolean modified = input.getGraphSize() > graphSize;
98
        for (final String f : fields) {
99
        for (final String f : fields) {
99
            // don't overwrite foreign rows and update modified
100
            // don't overwrite foreign rows and update modified
100
            if (!r.getFields().contains(f)) {
101
            if (!r.getFields().contains(f)) {
101
                r.put(f, null);
102
                r.put(f, null);
102
                modified = true;
103
                modified = true;
103
            }
104
            }
104
        }
105
        }
105
        return modified;
106
        return modified;
106
    }
107
    }
107
 
108
 
108
    private final SQLTable primaryTable;
109
    private final SQLTable primaryTable;
109
    @GuardedBy("this")
110
    @GuardedBy("this")
110
    private List<Path> order;
111
    private List<Path> order;
111
    @GuardedBy("this")
112
    @GuardedBy("this")
-
 
113
    private Map<Object, Where> wheres;
-
 
114
    @GuardedBy("this")
112
    private Where where;
115
    private Where where;
113
    @GuardedBy("this")
116
    @GuardedBy("this")
114
    private Map<IFieldPath, SearchField> searchFields;
117
    private Map<IFieldPath, SearchField> searchFields;
115
    @GuardedBy("this")
118
    @GuardedBy("this")
116
    private int searchLimit;
119
    private int searchLimit;
117
    @GuardedBy("this")
120
    @GuardedBy("this")
118
    private ITransformer<SQLSelect, SQLSelect> selTransf;
121
    private ITransformer<SQLSelect, SQLSelect> selTransf;
119
    @GuardedBy("this")
122
    @GuardedBy("this")
120
    private boolean lockSelect;
123
    private boolean lockSelect;
121
 
124
 
122
    private final SQLRowValues graph;
125
    private final SQLRowValues graph;
123
    @GuardedBy("this")
126
    @GuardedBy("this")
124
    private SQLRowValues graphToFetch;
127
    private SQLRowValues graphToFetch;
125
 
128
 
126
    @GuardedBy("this")
129
    @GuardedBy("this")
127
    private SQLRowValuesListFetcher frozen;
130
    private SQLRowValuesListFetcher frozen;
128
 
131
 
129
    {
132
    {
130
        // a new instance is never frozen
133
        // a new instance is never frozen
131
        this.frozen = null;
134
        this.frozen = null;
132
    }
135
    }
133
 
136
 
134
    private final PropertyChangeSupport supp = new PropertyChangeSupport(this);
137
    private final PropertyChangeSupport supp = new PropertyChangeSupport(this);
135
 
138
 
136
    public BaseFillSQLRequest(final SQLRowValues graph, final Where w) {
139
    public BaseFillSQLRequest(final SQLRowValues graph, final Where w) {
137
        super();
140
        super();
138
        if (graph == null)
141
        if (graph == null)
139
            throw new NullPointerException();
142
            throw new NullPointerException();
140
        this.primaryTable = graph.getTable();
143
        this.primaryTable = graph.getTable();
141
        this.setOrder(null);
144
        this.setOrder(null);
142
        this.where = w;
145
        this.setWhere(w);
143
        this.searchFields = Collections.emptyMap();
146
        this.searchFields = Collections.emptyMap();
144
        this.searchLimit = 35;
147
        this.searchLimit = 35;
145
        this.selTransf = null;
148
        this.selTransf = null;
146
        this.lockSelect = getDefaultLockSelect();
149
        this.lockSelect = getDefaultLockSelect();
147
        this.graph = graph.toImmutable();
150
        this.graph = graph.toImmutable();
148
        this.graphToFetch = null;
151
        this.graphToFetch = null;
149
    }
152
    }
150
 
153
 
151
    public BaseFillSQLRequest(final BaseFillSQLRequest req) {
154
    public BaseFillSQLRequest(final BaseFillSQLRequest req) {
152
        super();
155
        super();
153
        this.primaryTable = req.getPrimaryTable();
156
        this.primaryTable = req.getPrimaryTable();
154
        synchronized (req) {
157
        synchronized (req) {
155
            this.order = req.order;
158
            this.order = req.order;
-
 
159
            this.wheres = req.wheres;
156
            this.where = req.where;
160
            this.where = req.where;
157
            this.searchFields = req.searchFields;
161
            this.searchFields = req.searchFields;
158
            this.searchLimit = req.searchLimit;
162
            this.searchLimit = req.searchLimit;
159
            this.selTransf = req.selTransf;
163
            this.selTransf = req.selTransf;
160
            this.lockSelect = req.lockSelect;
164
            this.lockSelect = req.lockSelect;
161
            // use methods since they're both lazy
165
            // use methods since they're both lazy
162
            this.graph = req.getGraph();
166
            this.graph = req.getGraph();
163
            this.graphToFetch = req.getGraphToFetch();
167
            this.graphToFetch = req.getGraphToFetch();
164
        }
168
        }
165
    }
169
    }
166
 
170
 
167
    public synchronized final boolean isFrozen() {
171
    public synchronized final boolean isFrozen() {
168
        return this.frozen != null;
172
        return this.frozen != null;
169
    }
173
    }
170
 
174
 
171
    public final void freeze() {
175
    public final void freeze() {
172
        this.freeze(this);
176
        this.freeze(this);
173
    }
177
    }
174
 
178
 
175
    private final synchronized void freeze(final BaseFillSQLRequest from) {
179
    private final synchronized void freeze(final BaseFillSQLRequest from) {
176
        if (!this.isFrozen()) {
180
        if (!this.isFrozen()) {
177
            // compute the fetcher once and for all
181
            // compute the fetcher once and for all
178
            this.frozen = from.getFetcher();
182
            this.frozen = from.getFetcher();
179
            assert this.frozen.isFrozen();
183
            assert this.frozen.isFrozen();
180
            this.wasFrozen();
184
            this.wasFrozen();
181
        }
185
        }
182
    }
186
    }
183
 
187
 
184
    protected void wasFrozen() {
188
    protected void wasFrozen() {
185
    }
189
    }
186
 
190
 
187
    protected final void checkFrozen() {
191
    protected final void checkFrozen() {
188
        if (this.isFrozen())
192
        if (this.isFrozen())
189
            throw new IllegalStateException("this has been frozen: " + this);
193
            throw new IllegalStateException("this has been frozen: " + this);
190
    }
194
    }
191
 
195
 
192
    // not final so we can narrow down the return type
196
    // not final so we can narrow down the return type
193
    public BaseFillSQLRequest toUnmodifiable() {
197
    public BaseFillSQLRequest toUnmodifiable() {
194
        return this.toUnmodifiableP(this.getClass());
198
        return this.toUnmodifiableP(this.getClass());
195
    }
199
    }
196
 
200
 
197
    // should be passed the class created by cloneForFreeze(), i.e. not this.getClass() or this
201
    // should be passed the class created by cloneForFreeze(), i.e. not this.getClass() or this
198
    // won't support anonymous classes
202
    // won't support anonymous classes
199
    protected final <T extends BaseFillSQLRequest> T toUnmodifiableP(final Class<T> clazz) {
203
    protected final <T extends BaseFillSQLRequest> T toUnmodifiableP(final Class<T> clazz) {
200
        final Class<? extends BaseFillSQLRequest> thisClass = this.getClass();
204
        final Class<? extends BaseFillSQLRequest> thisClass = this.getClass();
201
        if (clazz != thisClass && !(thisClass.isAnonymousClass() && clazz == thisClass.getSuperclass()))
205
        if (clazz != thisClass && !(thisClass.isAnonymousClass() && clazz == thisClass.getSuperclass()))
202
            throw new IllegalArgumentException("Passed class isn't our class : " + clazz + " != " + thisClass);
206
            throw new IllegalArgumentException("Passed class isn't our class : " + clazz + " != " + thisClass);
203
        final BaseFillSQLRequest res;
207
        final BaseFillSQLRequest res;
204
        synchronized (this) {
208
        synchronized (this) {
205
            if (this.isFrozen()) {
209
            if (this.isFrozen()) {
206
                res = this;
210
                res = this;
207
            } else {
211
            } else {
208
                res = this.clone(true);
212
                res = this.clone(true);
209
                if (res.getClass() != clazz)
213
                if (res.getClass() != clazz)
210
                    throw new IllegalStateException("Clone class mismatch : " + res.getClass() + " != " + clazz);
214
                    throw new IllegalStateException("Clone class mismatch : " + res.getClass() + " != " + clazz);
211
                // freeze before releasing lock (even if not recommended, allow to modify the state
215
                // freeze before releasing lock (even if not recommended, allow to modify the state
212
                // of getSelectTransf() while holding our lock)
216
                // of getSelectTransf() while holding our lock)
213
                // pass ourselves so that if we are an anonymous class the fetcher created with our
217
                // pass ourselves so that if we are an anonymous class the fetcher created with our
214
                // overloaded methods is used
218
                // overloaded methods is used
215
                res.freeze(this);
219
                res.freeze(this);
216
            }
220
            }
217
        }
221
        }
218
        assert res.getClass() == clazz || res.getClass().getSuperclass() == clazz;
222
        assert res.getClass() == clazz || res.getClass().getSuperclass() == clazz;
219
        @SuppressWarnings("unchecked")
223
        @SuppressWarnings("unchecked")
220
        final T casted = (T) res;
224
        final T casted = (T) res;
221
        return casted;
225
        return casted;
222
    }
226
    }
223
 
227
 
224
    // must be called with our lock
228
    // must be called with our lock
225
    protected abstract BaseFillSQLRequest clone(boolean forFreeze);
229
    protected abstract BaseFillSQLRequest clone(boolean forFreeze);
226
 
230
 
227
    static protected final SQLRowValues computeGraph(final SQLTable t, final Collection<String> fields, final FieldExpander exp) {
231
    static protected final SQLRowValues computeGraph(final SQLTable t, final Collection<String> fields, final FieldExpander exp) {
228
        final SQLRowValues vals = new SQLRowValues(t).putNulls(fields);
232
        final SQLRowValues vals = new SQLRowValues(t).putNulls(fields);
229
        exp.expand(vals);
233
        exp.expand(vals);
230
        return vals.toImmutable();
234
        return vals.toImmutable();
231
    }
235
    }
232
 
236
 
233
    /**
237
    /**
234
     * The graph with fields to be automatically added to the UI.
238
     * The graph with fields to be automatically added to the UI.
235
     * 
239
     * 
236
     * @return the expanded frozen graph.
240
     * @return the expanded frozen graph.
237
     */
241
     */
238
    public final SQLRowValues getGraph() {
242
    public final SQLRowValues getGraph() {
239
        return this.graph;
243
        return this.graph;
240
    }
244
    }
241
 
245
 
242
    /**
246
    /**
243
     * The graph to fetch, should be a superset of {@link #getGraph()}. To modify it, see
247
     * The graph to fetch, should be a superset of {@link #getGraph()}. To modify it, see
244
     * {@link #addToGraphToFetch(Path, Set)} and {@link #changeGraphToFetch(IClosure)}.
248
     * {@link #addToGraphToFetch(Path, Set)} and {@link #changeGraphToFetch(IClosure)}.
245
     * 
249
     * 
246
     * @return the graph to fetch, frozen.
250
     * @return the graph to fetch, frozen.
247
     */
251
     */
248
    public final SQLRowValues getGraphToFetch() {
252
    public final SQLRowValues getGraphToFetch() {
249
        synchronized (this) {
253
        synchronized (this) {
250
            if (this.graphToFetch == null && this.getGraph() != null) {
254
            if (this.graphToFetch == null && this.getGraph() != null) {
251
                assert !this.isFrozen() : "no computation should take place after frozen()";
255
                assert !this.isFrozen() : "no computation should take place after frozen()";
252
                final SQLRowValues tmp = this.getGraph().deepCopy();
256
                final SQLRowValues tmp = this.getGraph().deepCopy();
253
                this.customizeToFetch(tmp);
257
                this.customizeToFetch(tmp);
254
                this.setGraphToFetch(tmp, true);
258
                this.setGraphToFetch(tmp, true);
255
            }
259
            }
256
            return this.graphToFetch;
260
            return this.graphToFetch;
257
        }
261
        }
258
    }
262
    }
259
 
263
 
260
    public final void addToGraphToFetch(final String... fields) {
264
    public final void addToGraphToFetch(final String... fields) {
261
        this.addToGraphToFetch(Arrays.asList(fields));
265
        this.addToGraphToFetch(Arrays.asList(fields));
262
    }
266
    }
263
 
267
 
264
    public final void addToGraphToFetch(final Collection<String> fields) {
268
    public final void addToGraphToFetch(final Collection<String> fields) {
265
        this.addToGraphToFetch(null, fields);
269
        this.addToGraphToFetch(null, fields);
266
    }
270
    }
267
 
271
 
268
    public final void addForeignToGraphToFetch(final String foreignField, final Collection<String> fields) {
272
    public final void addForeignToGraphToFetch(final String foreignField, final Collection<String> fields) {
269
        this.addToGraphToFetch(new Path(getPrimaryTable()).addForeignField(foreignField), fields);
273
        this.addToGraphToFetch(new Path(getPrimaryTable()).addForeignField(foreignField), fields);
270
    }
274
    }
271
 
275
 
272
    /**
276
    /**
273
     * Make sure that the fields at the end of the path are fetched.
277
     * Make sure that the fields at the end of the path are fetched.
274
     * 
278
     * 
275
     * @param p a path.
279
     * @param p a path.
276
     * @param fields fields to fetch.
280
     * @param fields fields to fetch.
277
     */
281
     */
278
    public final void addToGraphToFetch(final Path p, final Collection<String> fields) {
282
    public final void addToGraphToFetch(final Path p, final Collection<String> fields) {
279
        this.changeGraphToFetch(new IClosure<SQLRowValues>() {
283
        this.changeGraphToFetch(new IClosure<SQLRowValues>() {
280
            @Override
284
            @Override
281
            public void executeChecked(SQLRowValues input) {
285
            public void executeChecked(SQLRowValues input) {
282
                addToFetch(input, p, fields);
286
                addToFetch(input, p, fields);
283
            }
287
            }
284
        }, false);
288
        }, false);
285
    }
289
    }
286
 
290
 
287
    public final void changeGraphToFetch(IClosure<SQLRowValues> cl) {
291
    public final void changeGraphToFetch(IClosure<SQLRowValues> cl) {
288
        this.changeGraphToFetch(cl, true);
292
        this.changeGraphToFetch(cl, true);
289
    }
293
    }
290
 
294
 
291
    private final void changeGraphToFetch(IClosure<SQLRowValues> cl, final boolean checkNeeded) {
295
    private final void changeGraphToFetch(IClosure<SQLRowValues> cl, final boolean checkNeeded) {
292
        synchronized (this) {
296
        synchronized (this) {
293
            checkFrozen();
297
            checkFrozen();
294
            final SQLRowValues tmp = this.getGraphToFetch().deepCopy();
298
            final SQLRowValues tmp = this.getGraphToFetch().deepCopy();
295
            cl.executeChecked(tmp);
299
            cl.executeChecked(tmp);
296
            this.setGraphToFetch(tmp, checkNeeded);
300
            this.setGraphToFetch(tmp, checkNeeded);
297
        }
301
        }
298
        fireWhereChange();
302
        fireWhereChange();
299
    }
303
    }
300
 
304
 
301
    private final void setGraphToFetch(final SQLRowValues tmp, final boolean checkNeeded) {
305
    private final void setGraphToFetch(final SQLRowValues tmp, final boolean checkNeeded) {
302
        assert Thread.holdsLock(this) && !this.isFrozen();
306
        assert Thread.holdsLock(this) && !this.isFrozen();
303
        if (checkNeeded && !tmp.graphContains(this.getGraph()))
307
        if (checkNeeded && !tmp.graphContains(this.getGraph()))
304
            throw new IllegalArgumentException("New graph too small");
308
            throw new IllegalArgumentException("New graph too small");
305
        this.graphToFetch = tmp.toImmutable();
309
        this.graphToFetch = tmp.toImmutable();
306
    }
310
    }
307
 
311
 
308
    protected void customizeToFetch(final SQLRowValues graphToFetch) {
312
    protected void customizeToFetch(final SQLRowValues graphToFetch) {
309
    }
313
    }
310
 
314
 
311
    protected synchronized final SQLRowValuesListFetcher getFetcher() {
315
    protected synchronized final SQLRowValuesListFetcher getFetcher() {
312
        if (this.isFrozen())
316
        if (this.isFrozen())
313
            return this.frozen;
317
            return this.frozen;
314
        // fetch order fields, so that consumers can order an updated row in an existing list
318
        // fetch order fields, so that consumers can order an updated row in an existing list
315
        final SQLRowValues tmp = getGraphToFetch().deepCopy();
319
        final SQLRowValues tmp = getGraphToFetch().deepCopy();
316
        for (final Path orderP : this.getOrder()) {
320
        for (final Path orderP : this.getOrder()) {
317
            final SQLRowValues orderVals = tmp.followPath(orderP);
321
            final SQLRowValues orderVals = tmp.followPath(orderP);
318
            if (orderVals != null && orderVals.getTable().isOrdered()) {
322
            if (orderVals != null && orderVals.getTable().isOrdered()) {
319
                orderVals.put(orderVals.getTable().getOrderField().getName(), null);
323
                orderVals.put(orderVals.getTable().getOrderField().getName(), null);
320
            }
324
            }
321
        }
325
        }
322
        // graphToFetch can be modified freely so don't the use the simple constructor
326
        // graphToFetch can be modified freely so don't the use the simple constructor
323
        // order to have predictable result (this will both order the referent rows and main rows.
327
        // order to have predictable result (this will both order the referent rows and main rows.
324
        // The latter will be overwritten by our own getOrder())
328
        // The latter will be overwritten by our own getOrder())
325
        return setupFetcher(SQLRowValuesListFetcher.create(tmp, true));
329
        return setupFetcher(SQLRowValuesListFetcher.create(tmp, true));
326
    }
330
    }
327
 
331
 
328
    // allow to pass fetcher since they are mostly immutable (and for huge graphs they are slow to
332
    // allow to pass fetcher since they are mostly immutable (and for huge graphs they are slow to
329
    // create)
333
    // create)
330
    protected final SQLRowValuesListFetcher setupFetcher(final SQLRowValuesListFetcher fetcher) {
334
    protected final SQLRowValuesListFetcher setupFetcher(final SQLRowValuesListFetcher fetcher) {
331
        final String tableName = getPrimaryTable().getName();
335
        final String tableName = getPrimaryTable().getName();
332
        setupForeign(fetcher);
336
        setupForeign(fetcher);
333
        synchronized (this) {
337
        synchronized (this) {
334
            fetcher.setOrder(getOrder());
338
            fetcher.setOrder(getOrder());
335
            fetcher.setReturnedRowsUnmodifiable(true);
339
            fetcher.setReturnedRowsUnmodifiable(true);
336
            fetcher.appendSelTransf(new ITransformer<SQLSelect, SQLSelect>() {
340
            fetcher.appendSelTransf(new ITransformer<SQLSelect, SQLSelect>() {
337
                @Override
341
                @Override
338
                public SQLSelect transformChecked(SQLSelect sel) {
342
                public SQLSelect transformChecked(SQLSelect sel) {
339
                    sel = transformSelect(sel);
343
                    sel = transformSelect(sel);
340
                    if (isLockSelect())
344
                    if (isLockSelect())
341
                        sel.addLockedTable(tableName);
345
                        sel.addLockedTable(tableName);
342
                    return sel.andWhere(getWhere());
346
                    return sel.andWhere(getWhere());
343
                }
347
                }
344
            });
348
            });
345
            // freeze to execute setSelTransf() before leaving the synchronized block
349
            // freeze to execute setSelTransf() before leaving the synchronized block
346
            fetcher.freeze();
350
            fetcher.freeze();
347
        }
351
        }
348
        return fetcher;
352
        return fetcher;
349
    }
353
    }
350
 
354
 
351
    protected synchronized final List<Path> getOrder() {
355
    protected synchronized final List<Path> getOrder() {
352
        if (this.order != null)
356
        if (this.order != null)
353
            return this.order;
357
            return this.order;
354
        return this.getDefaultOrder();
358
        return this.getDefaultOrder();
355
    }
359
    }
356
 
360
 
357
    public final boolean isTableOrder() {
361
    public final boolean isTableOrder() {
358
        return this.getPrimaryTable().isOrdered() && this.getOrder().equals(getTableOrder());
362
        return this.getPrimaryTable().isOrdered() && this.getOrder().equals(getTableOrder());
359
    }
363
    }
360
 
364
 
361
    /**
365
    /**
362
     * Order the passed rows the same as {@link #getFetcher()}.
366
     * Order the passed rows the same as {@link #getFetcher()}.
363
     * 
367
     * 
364
     * @param r1 the first row.
368
     * @param r1 the first row.
365
     * @param r2 the second row.
369
     * @param r2 the second row.
366
     * @return a negative integer, zero, or a positive integer as the first argument is less than,
370
     * @return a negative integer, zero, or a positive integer as the first argument is less than,
367
     *         equal to, or greater than the second.
371
     *         equal to, or greater than the second.
368
     */
372
     */
369
    public final int order(final SQLRowValues r1, final SQLRowValues r2) {
373
    public final int order(final SQLRowValues r1, final SQLRowValues r2) {
370
        if (r1 == r2)
374
        if (r1 == r2)
371
            return 0;
375
            return 0;
372
        // same behaviour as SQLSelect
376
        // same behaviour as SQLSelect
373
        final Comparator<SQLRowAccessor> comp = OrderComparator.getFallbackToPKInstance();
377
        final Comparator<SQLRowAccessor> comp = OrderComparator.getFallbackToPKInstance();
374
        for (final Path p : getOrder()) {
378
        for (final Path p : getOrder()) {
375
            final SQLRowValues o1 = r1.followPath(p);
379
            final SQLRowValues o1 = r1.followPath(p);
376
            final SQLRowValues o2 = r2.followPath(p);
380
            final SQLRowValues o2 = r2.followPath(p);
377
            final int res = comp.compare(o1, o2);
381
            final int res = comp.compare(o1, o2);
378
            if (res != 0)
382
            if (res != 0)
379
                return res;
383
                return res;
380
        }
384
        }
381
        return 0;
385
        return 0;
382
    }
386
    }
383
 
387
 
-
 
388
    static private final VirtualFields FIELDS_FOR_ORDER = VirtualFields.PRIMARY_KEY.union(VirtualFields.ORDER);
-
 
389
 
-
 
390
    // allow to save memory by only keeping trimmed SQLRow
-
 
391
    public final OrderValue createOrderValue(final SQLRowValues r) {
-
 
392
        if (!r.isFrozen())
-
 
393
            throw new IllegalArgumentException("Row not frozen : " + r);
-
 
394
        final List<Path> order = getOrder();
-
 
395
        final List<SQLRow> rows = new ArrayList<>(order.size());
-
 
396
        for (final Path p : order) {
-
 
397
            rows.add(r.followPath(p).trimmedRow(FIELDS_FOR_ORDER));
-
 
398
        }
-
 
399
        return new OrderValue(Collections.unmodifiableList(rows));
-
 
400
    }
-
 
401
 
-
 
402
    static public final class OrderValue implements Comparable<OrderValue> {
-
 
403
        private final List<SQLRow> rows;
-
 
404
 
-
 
405
        OrderValue(List<SQLRow> rows) {
-
 
406
            super();
-
 
407
            this.rows = rows;
-
 
408
        }
-
 
409
 
-
 
410
        @Override
-
 
411
        public int compareTo(OrderValue o2) {
-
 
412
            if (this == o2)
-
 
413
                return 0;
-
 
414
            final int size = this.rows.size();
-
 
415
            if (size != o2.rows.size())
-
 
416
                throw new IllegalArgumentException("Not same state");
-
 
417
            // same behaviour as SQLSelect
-
 
418
            final Comparator<SQLRowAccessor> comp = OrderComparator.getFallbackToPKInstance();
-
 
419
            for (int i = 0; i < size; i++) {
-
 
420
                final SQLRow r1 = this.rows.get(i);
-
 
421
                final SQLRow r2 = o2.rows.get(i);
-
 
422
                final int res = comp.compare(r1, r2);
-
 
423
                if (res != 0)
-
 
424
                    return res;
-
 
425
            }
-
 
426
            return 0;
-
 
427
        }
-
 
428
    }
-
 
429
 
384
    protected List<Path> getDefaultOrder() {
430
    protected List<Path> getDefaultOrder() {
385
        return getTableOrder();
431
        return getTableOrder();
386
    }
432
    }
387
 
433
 
388
    protected final List<Path> getTableOrder() {
434
    protected final List<Path> getTableOrder() {
389
        return Collections.singletonList(Path.get(getPrimaryTable()));
435
        return Collections.singletonList(Path.get(getPrimaryTable()));
390
    }
436
    }
391
 
437
 
392
    /**
438
    /**
393
     * Change the ordering of this request.
439
     * Change the ordering of this request.
394
     * 
440
     * 
395
     * @param l the list of tables, <code>null</code> to restore the {@link #getDefaultOrder()
441
     * @param l the list of tables, <code>null</code> to restore the {@link #getDefaultOrder()
396
     *        default} .
442
     *        default} .
397
     */
443
     */
398
    public synchronized final void setOrder(List<Path> l) {
444
    public synchronized final void setOrder(List<Path> l) {
399
        checkFrozen();
445
        checkFrozen();
400
        this.order = l == null ? null : Collections.unmodifiableList(new ArrayList<Path>(l));
446
        this.order = l == null ? null : Collections.unmodifiableList(new ArrayList<Path>(l));
401
    }
447
    }
402
 
448
 
-
 
449
    /**
-
 
450
     * Set a where to be AND'd.
-
 
451
     * 
-
 
452
     * @param o a key, <code>null</code> to change the where set by {@link #setWhere(Where)}.
-
 
453
     * @param w the new value, <code>null</code> to remove.
-
 
454
     */
-
 
455
    public final void putWhere(final Object o, final Where w) {
-
 
456
        synchronized (this) {
-
 
457
            checkFrozen();
-
 
458
            final Map<Object, Where> newValue = new HashMap<>(this.wheres);
-
 
459
            if (w == null)
-
 
460
                newValue.remove(o);
-
 
461
            else
-
 
462
                newValue.put(o, w);
-
 
463
            this.wheres = Collections.unmodifiableMap(newValue);
-
 
464
            this.where = Where.and(this.wheres.values());
-
 
465
        }
-
 
466
        fireWhereChange();
-
 
467
    }
-
 
468
 
-
 
469
    /**
-
 
470
     * Set the where, replacing any other set by {@link #putWhere(Object, Where)}.
-
 
471
     * 
-
 
472
     * @param w the new value, <code>null</code> to remove.
-
 
473
     */
403
    public final void setWhere(final Where w) {
474
    public final void setWhere(final Where w) {
404
        synchronized (this) {
475
        synchronized (this) {
405
            checkFrozen();
476
            checkFrozen();
-
 
477
            this.wheres = w == null ? Collections.<Object, Where> emptyMap() : Collections.singletonMap(null, w);
406
            this.where = w;
478
            this.where = w;
407
        }
479
        }
408
        fireWhereChange();
480
        fireWhereChange();
409
    }
481
    }
410
 
482
 
411
    public synchronized final Where getWhere() {
483
    public synchronized final Where getWhere() {
412
        return this.where;
484
        return this.where;
413
    }
485
    }
414
 
486
 
415
    /**
487
    /**
416
     * Whether this request is searchable.
488
     * Whether this request is searchable.
417
     * 
489
     * 
418
     * @param b <code>true</code> if the {@link #getFields() local fields} should be used,
490
     * @param b <code>true</code> if the {@link #getFields() local fields} should be used,
419
     *        <code>false</code> to not be searchable.
491
     *        <code>false</code> to not be searchable.
420
     */
492
     */
421
    public final void setSearchable(final boolean b) {
493
    public final void setSearchable(final boolean b) {
422
        this.setSearchFields(b ? getDefaultSearchFields() : Collections.<SearchField> emptyList());
494
        this.setSearchFields(b ? getDefaultSearchFields() : Collections.<SearchField> emptyList());
423
    }
495
    }
424
 
496
 
425
    protected Collection<SearchField> getDefaultSearchFields() {
497
    protected Collection<SearchField> getDefaultSearchFields() {
426
        final Set<String> names = CollectionUtils.inter(this.getGraph().getFields(), this.getPrimaryTable().getFieldsNames(VirtualFields.LOCAL_CONTENT));
498
        final Set<String> names = CollectionUtils.inter(this.getGraph().getFields(), this.getPrimaryTable().getFieldsNames(VirtualFields.LOCAL_CONTENT));
427
        return mapOfModesToSearchFields(CollectionUtils.<String, SQLSearchMode> createMap(names));
499
        return mapOfModesToSearchFields(CollectionUtils.<String, SQLSearchMode> createMap(names));
428
    }
500
    }
429
 
501
 
430
    /**
502
    /**
431
     * Set the fields used to search.
503
     * Set the fields used to search.
432
     * 
504
     * 
433
     * @param searchFields only rows with these fields containing the terms will match.
505
     * @param searchFields only rows with these fields containing the terms will match.
434
     * @see #setSearch(String)
506
     * @see #setSearch(String)
435
     */
507
     */
436
    public final void setSearchFieldsNames(final Collection<String> searchFields) {
508
    public final void setSearchFieldsNames(final Collection<String> searchFields) {
437
        this.setSearchFieldsNames(CollectionUtils.<String, SQLSearchMode> createMap(searchFields));
509
        this.setSearchFieldsNames(CollectionUtils.<String, SQLSearchMode> createMap(searchFields));
438
    }
510
    }
439
 
511
 
440
    protected final Collection<SearchField> mapOfModesToSearchFields(Map<String, SQLSearchMode> searchFields) {
512
    protected final Collection<SearchField> mapOfModesToSearchFields(Map<String, SQLSearchMode> searchFields) {
441
        final List<SearchField> list = new ArrayList<SearchField>();
513
        final List<SearchField> list = new ArrayList<SearchField>();
442
        for (final Entry<String, SQLSearchMode> e : searchFields.entrySet()) {
514
        for (final Entry<String, SQLSearchMode> e : searchFields.entrySet()) {
443
            list.add(new SearchField(getPrimaryTable().getField(e.getKey()), e.getValue() == null ? SQLSearchMode.CONTAINS : e.getValue()));
515
            list.add(new SearchField(getPrimaryTable().getField(e.getKey()), e.getValue() == null ? SQLSearchMode.CONTAINS : e.getValue()));
444
        }
516
        }
445
        return list;
517
        return list;
446
    }
518
    }
447
 
519
 
448
    /**
520
    /**
449
     * Set the fields used to search.
521
     * Set the fields used to search.
450
     * 
522
     * 
451
     * @param searchFields for each field to search, how to match.
523
     * @param searchFields for each field to search, how to match.
452
     * @see #setSearch(String)
524
     * @see #setSearch(String)
453
     */
525
     */
454
    public final void setSearchFieldsNames(Map<String, SQLSearchMode> searchFields) {
526
    public final void setSearchFieldsNames(Map<String, SQLSearchMode> searchFields) {
455
        this.setSearchFields(mapOfModesToSearchFields(searchFields));
527
        this.setSearchFields(mapOfModesToSearchFields(searchFields));
456
    }
528
    }
457
 
529
 
458
    public final void setSearchFields(final Collection<SearchField> searchFields) {
530
    public final void setSearchFields(final Collection<SearchField> searchFields) {
459
        // can be outside the synchronized block, since it can't be reverted
531
        // can be outside the synchronized block, since it can't be reverted
460
        checkFrozen();
532
        checkFrozen();
461
        final Map<IFieldPath, SearchField> copy = new HashMap<IFieldPath, SearchField>();
533
        final Map<IFieldPath, SearchField> copy = new HashMap<IFieldPath, SearchField>();
462
        for (final SearchField f : searchFields) {
534
        for (final SearchField f : searchFields) {
463
            final SearchField prev = copy.put(f.getField(), f);
535
            final SearchField prev = copy.put(f.getField(), f);
464
            if (prev != null)
536
            if (prev != null)
465
                throw new IllegalArgumentException("Duplicate : " + f.getField());
537
                throw new IllegalArgumentException("Duplicate : " + f.getField());
466
        }
538
        }
467
        synchronized (this) {
539
        synchronized (this) {
468
            this.searchFields = Collections.unmodifiableMap(copy);
540
            this.searchFields = Collections.unmodifiableMap(copy);
469
        }
541
        }
470
        fireWhereChange();
542
        fireWhereChange();
471
    }
543
    }
472
 
544
 
473
    public Map<IFieldPath, SearchField> getSearchFields() {
545
    public Map<IFieldPath, SearchField> getSearchFields() {
474
        synchronized (this) {
546
        synchronized (this) {
475
            return this.searchFields;
547
            return this.searchFields;
476
        }
548
        }
477
    }
549
    }
478
 
550
 
479
    public synchronized final boolean isSearchable() {
551
    public synchronized final boolean isSearchable() {
480
        return !this.getSearchFields().isEmpty();
552
        return !this.getSearchFields().isEmpty();
481
    }
553
    }
482
 
554
 
483
    public synchronized final void setSearchLimit(final int limit) {
555
    public synchronized final void setSearchLimit(final int limit) {
484
        this.searchLimit = limit;
556
        this.searchLimit = limit;
485
    }
557
    }
486
 
558
 
487
    public synchronized final int getSearchLimit() {
559
    public synchronized final int getSearchLimit() {
488
        return this.searchLimit;
560
        return this.searchLimit;
489
    }
561
    }
490
 
562
 
491
    public final synchronized void setLockSelect(boolean lockSelect) {
563
    public final synchronized void setLockSelect(boolean lockSelect) {
492
        checkFrozen();
564
        checkFrozen();
493
        this.lockSelect = lockSelect;
565
        this.lockSelect = lockSelect;
494
    }
566
    }
495
 
567
 
496
    public final synchronized boolean isLockSelect() {
568
    public final synchronized boolean isLockSelect() {
497
        return this.lockSelect;
569
        return this.lockSelect;
498
    }
570
    }
499
 
571
 
500
    public Set<SQLTable> getTables() {
572
    public Set<SQLTable> getTables() {
501
        final Set<SQLTable> res = new HashSet<SQLTable>();
573
        final Set<SQLTable> res = new HashSet<SQLTable>();
502
        for (final SQLRowValues v : this.getGraphToFetch().getGraph().getItems())
574
        for (final SQLRowValues v : this.getGraphToFetch().getGraph().getItems())
503
            res.add(v.getTable());
575
            res.add(v.getTable());
504
        return res;
576
        return res;
505
    }
577
    }
506
 
578
 
507
    public final void addTableListener(SQLTableModifiedListener l) {
579
    public final void addTableListener(SQLTableModifiedListener l) {
508
        for (final SQLTable t : this.getTables()) {
580
        for (final SQLTable t : this.getTables()) {
509
            t.addTableModifiedListener(l);
581
            t.addTableModifiedListener(l);
510
        }
582
        }
511
    }
583
    }
512
 
584
 
513
    public final void removeTableListener(SQLTableModifiedListener l) {
585
    public final void removeTableListener(SQLTableModifiedListener l) {
514
        for (final SQLTable t : this.getTables()) {
586
        for (final SQLTable t : this.getTables()) {
515
            t.removeTableModifiedListener(l);
587
            t.removeTableModifiedListener(l);
516
        }
588
        }
517
    }
589
    }
518
 
590
 
519
    protected final List<SQLField> getFields() {
591
    protected final List<SQLField> getFields() {
520
        return this.getPrimaryTable().getFields(this.getGraph().getFields());
592
        return this.getPrimaryTable().getFields(this.getGraph().getFields());
521
    }
593
    }
522
 
594
 
523
    protected SQLSelect transformSelect(final SQLSelect sel) {
595
    protected SQLSelect transformSelect(final SQLSelect sel) {
524
        final ITransformer<SQLSelect, SQLSelect> transf = this.getSelectTransf();
596
        final ITransformer<SQLSelect, SQLSelect> transf = this.getSelectTransf();
525
        return transf == null ? sel : transf.transformChecked(sel);
597
        return transf == null ? sel : transf.transformChecked(sel);
526
    }
598
    }
527
 
599
 
528
    // @param searchQuery null means don't want to search in SQL (i.e. no WHERE, no LIMIT), empty
600
    // @param searchQuery null means don't want to search in SQL (i.e. no WHERE, no LIMIT), empty
529
    // means nothing to search (i.e. no WHERE but LIMIT).
601
    // means nothing to search (i.e. no WHERE but LIMIT).
530
    protected final ITransformer<SQLSelect, SQLSelect> createSearchTransformer(final List<String> searchQuery, final Locale l, final Where forceInclude) {
602
    protected final ITransformer<SQLSelect, SQLSelect> createSearchTransformer(final List<String> searchQuery, final Locale l, final Where forceInclude) {
531
        if (searchQuery == null)
603
        if (searchQuery == null)
532
            return null;
604
            return null;
533
        final Map<IFieldPath, SearchField> searchFields;
605
        final Map<IFieldPath, SearchField> searchFields;
534
        final int searchLimit;
606
        final int searchLimit;
535
        final boolean searchable;
607
        final boolean searchable;
536
        synchronized (this) {
608
        synchronized (this) {
537
            searchFields = this.getSearchFields();
609
            searchFields = this.getSearchFields();
538
            searchLimit = this.getSearchLimit();
610
            searchLimit = this.getSearchLimit();
539
            searchable = this.isSearchable();
611
            searchable = this.isSearchable();
540
        }
612
        }
541
        if (!searchable) {
613
        if (!searchable) {
542
            throw new IllegalArgumentException("Cannot search " + searchQuery);
614
            throw new IllegalArgumentException("Cannot search " + searchQuery);
543
        }
615
        }
544
        // continue even if searchQuery is empty to apply the LIMIT
616
        // continue even if searchQuery is empty to apply the LIMIT
545
        final List<String> immutableQuery = Collections.unmodifiableList(new ArrayList<String>(searchQuery));
617
        final List<String> immutableQuery = Collections.unmodifiableList(new ArrayList<String>(searchQuery));
546
        return new ITransformer<SQLSelect, SQLSelect>() {
618
        return new ITransformer<SQLSelect, SQLSelect>() {
547
            @Override
619
            @Override
548
            public SQLSelect transformChecked(SQLSelect sel) {
620
            public SQLSelect transformChecked(SQLSelect sel) {
549
                return transformSelectSearch(sel, searchFields, searchLimit, immutableQuery, l, forceInclude);
621
                return transformSelectSearch(sel, searchFields, searchLimit, immutableQuery, l, forceInclude);
550
            }
622
            }
551
        };
623
        };
552
    }
624
    }
553
 
625
 
554
    static protected final SQLSelect transformSelectSearch(final SQLSelect sel, final Map<IFieldPath, SearchField> searchFields, final int searchLimit, final List<String> searchQuery, final Locale l,
626
    static protected final SQLSelect transformSelectSearch(final SQLSelect sel, final Map<IFieldPath, SearchField> searchFields, final int searchLimit, final List<String> searchQuery, final Locale l,
555
            final Where forceInclude) {
627
            final Where forceInclude) {
556
        final Where w;
628
        final Where w;
557
        final Set<String> matchScore = new HashSet<String>();
629
        final Set<String> matchScore = new HashSet<String>();
558
        if (!searchQuery.isEmpty()) {
630
        if (!searchQuery.isEmpty()) {
559
            final SQLSyntax syntax = sel.getSyntax();
631
            final SQLSyntax syntax = sel.getSyntax();
560
            Where where = null;
632
            Where where = null;
561
            for (final String searchTerm : searchQuery) {
633
            for (final String searchTerm : searchQuery) {
562
                Where termWhere = null;
634
                Where termWhere = null;
563
                for (final SearchField searchField : searchFields.values()) {
635
                for (final SearchField searchField : searchFields.values()) {
564
                    final FieldRef selF = sel.followFieldPath(searchField.getField());
636
                    final FieldRef selF = sel.followFieldPath(searchField.getField());
565
                    final SQLSearchMode mode = searchField.getMode();
637
                    final SQLSearchMode mode = searchField.getMode();
566
                    final List<String> formatted = searchField.format(selF, l);
638
                    final List<String> formatted = searchField.format(selF, l);
567
                    final String fieldWhere = createWhere(syntax, formatted, mode, searchTerm);
639
                    final String fieldWhere = createWhere(syntax, formatted, mode, searchTerm);
568
                    termWhere = Where.createRaw(fieldWhere).or(termWhere);
640
                    termWhere = Where.createRaw(fieldWhere).or(termWhere);
569
                    if (searchField.getScore() > 0 || !searchField.getHigherModes().isEmpty()) {
641
                    if (searchField.getScore() > 0 || !searchField.getHigherModes().isEmpty()) {
570
                        final CaseBuilder caseBuilder = syntax.createCaseWhenBuilder().setElse("0");
642
                        final CaseBuilder caseBuilder = syntax.createCaseWhenBuilder().setElse("0");
571
                        for (final Tuple2<SQLSearchMode, Integer> hm : searchField.getHigherModes()) {
643
                        for (final Tuple2<SQLSearchMode, Integer> hm : searchField.getHigherModes()) {
572
                            caseBuilder.addWhen(createWhere(syntax, formatted, hm.get0(), searchTerm), String.valueOf(hm.get1()));
644
                            caseBuilder.addWhen(createWhere(syntax, formatted, hm.get0(), searchTerm), String.valueOf(hm.get1()));
573
                        }
645
                        }
574
                        if (searchField.getScore() > 0) {
646
                        if (searchField.getScore() > 0) {
575
                            caseBuilder.addWhen(fieldWhere, String.valueOf(searchField.getScore()));
647
                            caseBuilder.addWhen(fieldWhere, String.valueOf(searchField.getScore()));
576
                        }
648
                        }
577
                        matchScore.add(caseBuilder.build());
649
                        matchScore.add(caseBuilder.build());
578
                    }
650
                    }
579
                }
651
                }
580
                where = Where.and(termWhere, where);
652
                where = Where.and(termWhere, where);
581
            }
653
            }
582
            // only use forceInclude when there's a restriction otherwise the include transforms
654
            // only use forceInclude when there's a restriction otherwise the include transforms
583
            // itself into a restrict
655
            // itself into a restrict
584
            if (where != null)
656
            if (where != null)
585
                where = where.or(forceInclude);
657
                where = where.or(forceInclude);
586
            w = where;
658
            w = where;
587
        } else {
659
        } else {
588
            w = null;
660
            w = null;
589
        }
661
        }
590
        sel.andWhere(w);
662
        sel.andWhere(w);
591
        if (forceInclude != null)
663
        if (forceInclude != null)
592
            matchScore.add("case when " + forceInclude + " then 10000 else 0 end");
664
            matchScore.add("case when " + forceInclude + " then 10000 else 0 end");
593
        if (!matchScore.isEmpty())
665
        if (!matchScore.isEmpty())
594
            sel.getOrder().add(0, CollectionUtils.join(matchScore, " + ") + " DESC");
666
            sel.getOrder().add(0, CollectionUtils.join(matchScore, " + ") + " DESC");
595
        if (searchLimit >= 0)
667
        if (searchLimit >= 0)
596
            sel.setLimit(searchLimit);
668
            sel.setLimit(searchLimit);
597
 
669
 
598
        return sel;
670
        return sel;
599
    }
671
    }
600
 
672
 
601
    static protected final String createWhere(final SQLSyntax syntax, final List<String> formatted, final SQLSearchMode mode, final String searchQuery) {
673
    static protected final String createWhere(final SQLSyntax syntax, final List<String> formatted, final SQLSearchMode mode, final String searchQuery) {
602
        return CollectionUtils.join(formatted, " OR ", new ITransformer<String, String>() {
674
        return CollectionUtils.join(formatted, " OR ", new ITransformer<String, String>() {
603
            @Override
675
            @Override
604
            public String transformChecked(String sqlExpr) {
676
            public String transformChecked(String sqlExpr) {
605
                return createWhere(sqlExpr, mode, syntax, searchQuery);
677
                return createWhere(sqlExpr, mode, syntax, searchQuery);
606
            }
678
            }
607
        });
679
        });
608
    }
680
    }
609
 
681
 
610
    static public final List<String> defaultFormat(final FieldRef selF, final Locale l) {
682
    static public final List<String> defaultFormat(final FieldRef selF, final Locale l) {
611
        final SQLType type = selF.getField().getType();
683
        final SQLType type = selF.getField().getType();
612
        final SQLSyntax syntax = SQLSyntax.get(selF.getField());
684
        final SQLSyntax syntax = SQLSyntax.get(selF.getField());
613
        if (type.getJavaType() == String.class) {
685
        if (type.getJavaType() == String.class) {
614
            return Collections.singletonList(selF.getFieldRef());
686
            return Collections.singletonList(selF.getFieldRef());
615
        } else if (type.getJavaType() == Boolean.class) {
687
        } else if (type.getJavaType() == Boolean.class) {
616
            final org.openconcerto.utils.i18n.TM utilsTM = org.openconcerto.utils.i18n.TM.getInstance(l);
688
            final org.openconcerto.utils.i18n.TM utilsTM = org.openconcerto.utils.i18n.TM.getInstance(l);
617
            return Collections.singletonList(
689
            return Collections.singletonList(
618
                    "case when " + selF.getFieldRef() + " then " + syntax.quoteString(utilsTM.translate("true_key")) + " else " + syntax.quoteString(utilsTM.translate("false_key")) + " end");
690
                    "case when " + selF.getFieldRef() + " then " + syntax.quoteString(utilsTM.translate("true_key")) + " else " + syntax.quoteString(utilsTM.translate("false_key")) + " end");
619
        } else if (Timestamp.class.isAssignableFrom(type.getJavaType())) {
691
        } else if (Timestamp.class.isAssignableFrom(type.getJavaType())) {
620
            final String shortFmt = formatTime(selF, DateProp.SHORT_DATETIME_SKELETON, l, syntax);
692
            final String shortFmt = formatTime(selF, DateProp.SHORT_DATETIME_SKELETON, l, syntax);
621
            final String longFmt = formatTime(selF, DateProp.LONG_DATETIME_SKELETON, l, syntax);
693
            final String longFmt = formatTime(selF, DateProp.LONG_DATETIME_SKELETON, l, syntax);
622
            return Arrays.asList(shortFmt, longFmt);
694
            return Arrays.asList(shortFmt, longFmt);
623
        } else if (Time.class.isAssignableFrom(type.getJavaType())) {
695
        } else if (Time.class.isAssignableFrom(type.getJavaType())) {
624
            return Collections.singletonList(formatTime(selF, DateProp.TIME_SKELETON, l, syntax));
696
            return Collections.singletonList(formatTime(selF, DateProp.TIME_SKELETON, l, syntax));
625
        } else if (Date.class.isAssignableFrom(type.getJavaType())) {
697
        } else if (Date.class.isAssignableFrom(type.getJavaType())) {
626
            final String shortFmt = formatTime(selF, DateProp.SHORT_DATE_SKELETON, l, syntax);
698
            final String shortFmt = formatTime(selF, DateProp.SHORT_DATE_SKELETON, l, syntax);
627
            final String longFmt = formatTime(selF, DateProp.LONG_DATE_SKELETON, l, syntax);
699
            final String longFmt = formatTime(selF, DateProp.LONG_DATE_SKELETON, l, syntax);
628
            return Arrays.asList(shortFmt, longFmt);
700
            return Arrays.asList(shortFmt, longFmt);
629
        } else {
701
        } else {
630
            return Collections.singletonList(syntax.cast(selF.getFieldRef(), String.class));
702
            return Collections.singletonList(syntax.cast(selF.getFieldRef(), String.class));
631
        }
703
        }
632
    }
704
    }
633
 
705
 
634
    static public final String formatTime(final FieldRef selF, final List<String> simpleFormat, final Locale l, final SQLSyntax syntax) {
706
    static public final String formatTime(final FieldRef selF, final List<String> simpleFormat, final Locale l, final SQLSyntax syntax) {
635
        return syntax.getFormatTimestampSimple(selF.getFieldRef(), DateProp.getBestPattern(simpleFormat, l), l);
707
        return syntax.getFormatTimestampSimple(selF.getFieldRef(), DateProp.getBestPattern(simpleFormat, l), l);
636
    }
708
    }
637
 
709
 
638
    static protected final String createWhere(final String sqlExpr, final SQLSearchMode mode, final SQLSyntax syntax, final String searchQuery) {
710
    static protected final String createWhere(final String sqlExpr, final SQLSearchMode mode, final SQLSyntax syntax, final String searchQuery) {
639
        return "lower(" + sqlExpr + ") " + mode.generateSQL(syntax, searchQuery.toLowerCase());
711
        return "lower(" + sqlExpr + ") " + mode.generateSQL(syntax, searchQuery.toLowerCase());
640
    }
712
    }
641
 
713
 
642
    static public class SearchField {
714
    static public class SearchField {
643
        private final IFieldPath field;
715
        private final IFieldPath field;
644
        private final SQLSearchMode mode;
716
        private final SQLSearchMode mode;
645
        private final int score;
717
        private final int score;
646
        private final List<Tuple2<SQLSearchMode, Integer>> higherModes;
718
        private final List<Tuple2<SQLSearchMode, Integer>> higherModes;
647
 
719
 
648
        public SearchField(IFieldPath field, SQLSearchMode mode) {
720
        public SearchField(IFieldPath field, SQLSearchMode mode) {
649
            this(field, mode, 1);
721
            this(field, mode, 1);
650
        }
722
        }
651
 
723
 
652
        /**
724
        /**
653
         * Create a new search field.
725
         * Create a new search field.
654
         * 
726
         * 
655
         * @param field which field to search.
727
         * @param field which field to search.
656
         * @param mode how to search.
728
         * @param mode how to search.
657
         * @param score the score (>0) to attribute if the field matches. Allow to rank fields
729
         * @param score the score (>0) to attribute if the field matches. Allow to rank fields
658
         *        between themselves.
730
         *        between themselves.
659
         */
731
         */
660
        public SearchField(IFieldPath field, SQLSearchMode mode, int score) {
732
        public SearchField(IFieldPath field, SQLSearchMode mode, int score) {
661
            this(field, mode, score, -1, -1);
733
            this(field, mode, score, -1, -1);
662
        }
734
        }
663
 
735
 
664
        public SearchField(final IFieldPath field, final SQLSearchMode mode, final int score, final int score2, final int score3) {
736
        public SearchField(final IFieldPath field, final SQLSearchMode mode, final int score, final int score2, final int score3) {
665
            super();
737
            super();
666
            if (field.getField().getFieldGroup().getKeyType() != null)
738
            if (field.getField().getFieldGroup().getKeyType() != null)
667
                throw new IllegalArgumentException("Field is a key : " + field);
739
                throw new IllegalArgumentException("Field is a key : " + field);
668
            this.field = field;
740
            this.field = field;
669
            this.mode = mode;
741
            this.mode = mode;
670
            /*
742
            /*
671
             * for now we could pass <code>1</code> so that a row with more matches is higher ranked
743
             * for now we could pass <code>1</code> so that a row with more matches is higher ranked
672
             * (e.g. if searching "a" ["ant", "cat"] is better than ["ant", "horse"]), or
744
             * (e.g. if searching "a" ["ant", "cat"] is better than ["ant", "horse"]), or
673
             * <code>0</code> to ignore the match count. But this only works because we have
745
             * <code>0</code> to ignore the match count. But this only works because we have
674
             * separate WHERE and ORDER BY ; if we had a computed column with "WHERE score > 0 ORDER
746
             * separate WHERE and ORDER BY ; if we had a computed column with "WHERE score > 0 ORDER
675
             * BY score" this would be complicated.
747
             * BY score" this would be complicated.
676
             */
748
             */
677
            if (score < 1)
749
            if (score < 1)
678
                throw new IllegalArgumentException("Invalid score : " + score);
750
                throw new IllegalArgumentException("Invalid score : " + score);
679
            this.score = score;
751
            this.score = score;
680
            final List<SQLSearchMode> higherModes = field.getField().getType().getJavaType() == String.class ? this.mode.getHigherModes() : Collections.<SQLSearchMode> emptyList();
752
            final List<SQLSearchMode> higherModes = field.getField().getType().getJavaType() == String.class ? this.mode.getHigherModes() : Collections.<SQLSearchMode> emptyList();
681
            if (higherModes.isEmpty()) {
753
            if (higherModes.isEmpty()) {
682
                this.higherModes = Collections.emptyList();
754
                this.higherModes = Collections.emptyList();
683
            } else {
755
            } else {
684
                if (higherModes.size() > 2)
756
                if (higherModes.size() > 2)
685
                    throw new IllegalStateException("Too many higher modes " + higherModes);
757
                    throw new IllegalStateException("Too many higher modes " + higherModes);
686
                final List<Tuple2<SQLSearchMode, Integer>> tmp = new ArrayList<Tuple2<SQLSearchMode, Integer>>(2);
758
                final List<Tuple2<SQLSearchMode, Integer>> tmp = new ArrayList<Tuple2<SQLSearchMode, Integer>>(2);
687
                tmp.add(Tuple2.create(higherModes.get(0), score3 < 1 ? Math.max((int) (this.score * 1.5), this.score + 2) : score3));
759
                tmp.add(Tuple2.create(higherModes.get(0), score3 < 1 ? Math.max((int) (this.score * 1.5), this.score + 2) : score3));
688
                if (higherModes.size() > 1)
760
                if (higherModes.size() > 1)
689
                    tmp.add(Tuple2.create(higherModes.get(1), score2 < 1 ? Math.max((int) (this.score * 1.2), this.score + 1) : score2));
761
                    tmp.add(Tuple2.create(higherModes.get(1), score2 < 1 ? Math.max((int) (this.score * 1.2), this.score + 1) : score2));
690
                this.higherModes = Collections.unmodifiableList(tmp);
762
                this.higherModes = Collections.unmodifiableList(tmp);
691
            }
763
            }
692
        }
764
        }
693
 
765
 
694
        public final IFieldPath getField() {
766
        public final IFieldPath getField() {
695
            return this.field;
767
            return this.field;
696
        }
768
        }
697
 
769
 
698
        public final SQLSearchMode getMode() {
770
        public final SQLSearchMode getMode() {
699
            return this.mode;
771
            return this.mode;
700
        }
772
        }
701
 
773
 
702
        public final int getScore() {
774
        public final int getScore() {
703
            return this.score;
775
            return this.score;
704
        }
776
        }
705
 
777
 
706
        public List<Tuple2<SQLSearchMode, Integer>> getHigherModes() {
778
        public List<Tuple2<SQLSearchMode, Integer>> getHigherModes() {
707
            return this.higherModes;
779
            return this.higherModes;
708
        }
780
        }
709
 
781
 
710
        protected List<String> format(final FieldRef selF, final Locale l) {
782
        protected List<String> format(final FieldRef selF, final Locale l) {
711
            if (getField().getField() != selF.getField())
783
            if (getField().getField() != selF.getField())
712
                throw new IllegalArgumentException("Wrong field");
784
                throw new IllegalArgumentException("Wrong field");
713
            return defaultFormat(selF, l);
785
            return defaultFormat(selF, l);
714
        }
786
        }
715
    }
787
    }
716
 
788
 
717
    public final synchronized ITransformer<SQLSelect, SQLSelect> getSelectTransf() {
789
    public final synchronized ITransformer<SQLSelect, SQLSelect> getSelectTransf() {
718
        return this.selTransf;
790
        return this.selTransf;
719
    }
791
    }
720
 
792
 
721
    /**
793
    /**
722
     * Allows to transform the SQLSelect returned by getFillRequest().
794
     * Allows to transform the SQLSelect returned by getFillRequest().
723
     * 
795
     * 
724
     * @param transf the transformer to apply, needs to be thread-safe.
796
     * @param transf the transformer to apply, needs to be thread-safe.
725
     */
797
     */
726
    public final void setSelectTransf(final ITransformer<SQLSelect, SQLSelect> transf) {
798
    public final void setSelectTransf(final ITransformer<SQLSelect, SQLSelect> transf) {
727
        synchronized (this) {
799
        synchronized (this) {
728
            checkFrozen();
800
            checkFrozen();
729
            this.selTransf = transf;
801
            this.selTransf = transf;
730
        }
802
        }
731
        this.fireWhereChange();
803
        this.fireWhereChange();
732
    }
804
    }
733
 
805
 
734
    public final SQLTable getPrimaryTable() {
806
    public final SQLTable getPrimaryTable() {
735
        return this.primaryTable;
807
        return this.primaryTable;
736
    }
808
    }
737
 
809
 
738
    protected final void fireWhereChange() {
810
    protected final void fireWhereChange() {
739
        // don't call unknown code with our lock
811
        // don't call unknown code with our lock
740
        assert !Thread.holdsLock(this);
812
        assert !Thread.holdsLock(this);
741
        this.supp.firePropertyChange("where", null, null);
813
        this.supp.firePropertyChange("where", null, null);
742
    }
814
    }
743
 
815
 
744
    public final void addWhereListener(final PropertyChangeListener l) {
816
    public final void addWhereListener(final PropertyChangeListener l) {
745
        this.supp.addPropertyChangeListener("where", l);
817
        this.supp.addPropertyChangeListener("where", l);
746
    }
818
    }
747
 
819
 
748
    public final void rmWhereListener(final PropertyChangeListener l) {
820
    public final void rmWhereListener(final PropertyChangeListener l) {
749
        this.supp.removePropertyChangeListener("where", l);
821
        this.supp.removePropertyChangeListener("where", l);
750
    }
822
    }
751
 
823
 
752
    @Override
824
    @Override
753
    public String toString() {
825
    public String toString() {
754
        return this.getClass().getName() + " on " + this.getPrimaryTable();
826
        return this.getClass().getName() + " on " + this.getPrimaryTable();
755
    }
827
    }
756
}
828
}