-
Notifications
You must be signed in to change notification settings - Fork 188
/
ItemRestriction.java
601 lines (513 loc) · 25.1 KB
/
ItemRestriction.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
/*
* Copyright (c) 2010-2013 Evolveum
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.evolveum.midpoint.repo.sql.query.restriction;
import com.evolveum.midpoint.prism.PrismPropertyValue;
import com.evolveum.midpoint.prism.PrismValue;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.path.ItemPathSegment;
import com.evolveum.midpoint.prism.path.NameItemPathSegment;
import com.evolveum.midpoint.prism.polystring.PolyString;
import com.evolveum.midpoint.prism.query.*;
import com.evolveum.midpoint.repo.sql.data.common.enums.SchemaEnum;
import com.evolveum.midpoint.repo.sql.data.common.other.RObjectType;
import com.evolveum.midpoint.repo.sql.query.QueryContext;
import com.evolveum.midpoint.repo.sql.query.QueryDefinitionRegistry;
import com.evolveum.midpoint.repo.sql.query.QueryException;
import com.evolveum.midpoint.repo.sql.query.QueryInterpreter;
import com.evolveum.midpoint.repo.sql.query.definition.*;
import com.evolveum.midpoint.repo.sql.query.matcher.Matcher;
import com.evolveum.midpoint.repo.sql.util.ClassMapper;
import com.evolveum.midpoint.repo.sql.util.RUtil;
import com.evolveum.midpoint.schema.constants.ObjectTypes;
import com.evolveum.midpoint.util.QNameUtil;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;
import com.evolveum.prism.xml.ns._public.types_3.PolyStringType;
import org.apache.commons.lang.Validate;
import org.hibernate.Criteria;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Restrictions;
import org.hibernate.sql.JoinType;
import javax.xml.namespace.QName;
import java.util.*;
/**
* @author lazyman
*/
public abstract class ItemRestriction<T extends ValueFilter> extends Restriction<T> {
private static final Trace LOGGER = TraceManager.getTrace(ItemRestriction.class);
@Override
public boolean canHandle(ObjectFilter filter) throws QueryException {
Validate.notNull(filter, "Object filter must not be null.");
if (!(filter instanceof ValueFilter)) {
return false;
}
return true;
}
@Override
public Criterion interpret() throws QueryException {
ItemPath path = filter.getFullPath();
if (path != null) {
// at first we build criterias with aliases
updateQueryContext(path);
}
Criterion main = interpretInternal(filter);
Criterion virtual = createVirtualCriterion(path);
if (virtual != null) {
return Restrictions.and(virtual, main);
}
return main;
}
public abstract Criterion interpretInternal(T filter) throws QueryException;
private TypeRestriction findTypeRestrictionParent(Restriction restriction) {
if (restriction == null) {
return null;
}
if (restriction instanceof TypeRestriction) {
return (TypeRestriction) restriction;
}
return findTypeRestrictionParent(restriction.getParent());
}
private Set<Class<? extends ObjectType>> findOtherPossibleParents() {
TypeRestriction typeRestriction = findTypeRestrictionParent(this);
ObjectTypes typeClass;
if (typeRestriction != null) {
TypeFilter filter = typeRestriction.getFilter();
typeClass = ObjectTypes.getObjectTypeFromTypeQName(filter.getType());
} else {
typeClass = ObjectTypes.getObjectType(getContext().getType());
}
Set<Class<? extends ObjectType>> classes = new HashSet<>();
classes.add(typeClass.getClassDefinition());
switch (typeClass) {
case OBJECT:
classes.addAll(ObjectTypes.getAllObjectTypes());
break;
case FOCUS_TYPE:
classes.add(UserType.class);
case ABSTRACT_ROLE:
classes.add(RoleType.class);
classes.add(OrgType.class);
}
LOGGER.trace("Found possible parents {} for entity definitions.", Arrays.toString(classes.toArray()));
return classes;
}
protected <T extends Definition> T findProperDefinition(ItemPath path, Class<T> clazz) {
QueryContext context = getContext();
QueryDefinitionRegistry registry = QueryDefinitionRegistry.getInstance();
if (!ObjectType.class.equals(context.getType())) {
return registry.findDefinition(context.getType(), path, clazz);
}
//we should try to find property in descendant classes
for (Class type : findOtherPossibleParents()) {
Definition def = registry.findDefinition(type, path, clazz);
if (def != null) {
return (T) def;
}
}
return null;
}
protected EntityDefinition findProperEntityDefinition(ItemPath path) {
QueryContext context = getContext();
QueryDefinitionRegistry registry = QueryDefinitionRegistry.getInstance();
if (!ObjectType.class.equals(context.getType())) {
return registry.findDefinition(context.getType(), null, EntityDefinition.class);
}
EntityDefinition entity = null;
// we should try to find property in descendant classes
for (Class type : findOtherPossibleParents()) {
entity = registry.findDefinition(type, null, EntityDefinition.class);
Definition def = entity.findDefinition(path, Definition.class);
if (def != null) {
break;
}
}
LOGGER.trace("Found proper entity definition for path {}, {}", path, entity.toString());
return entity;
}
//todo reimplement, use DefinitionHandlers or maybe another great concept
private void updateQueryContext(ItemPath path) throws QueryException {
LOGGER.trace("Updating query context based on path {}", new Object[]{path.toString()});
EntityDefinition definition = findProperEntityDefinition(path);
List<ItemPathSegment> segments = path.getSegments();
List<ItemPathSegment> propPathSegments = new ArrayList<ItemPathSegment>();
ItemPath propPath;
for (ItemPathSegment segment : segments) {
QName qname = ItemPath.getName(segment);
if (ObjectType.F_METADATA.equals(qname)) {
continue;
}
// ugly hack: construction/resourceRef -> resourceRef
if (QNameUtil.match(AssignmentType.F_CONSTRUCTION, qname)) {
continue;
}
// create new property path
propPathSegments.add(new NameItemPathSegment(qname));
propPath = new ItemPath(propPathSegments);
// get entity query definition
if (QNameUtil.match(qname, ObjectType.F_EXTENSION) || QNameUtil.match(qname, ShadowType.F_ATTRIBUTES)) {
break;
}
Definition childDef = definition.findDefinition(qname, Definition.class);
if (childDef == null) {
throw new QueryException("Definition '" + definition + "' doesn't contain child definition '"
+ qname + "'. Please check your path in query, or query entity/attribute mappings. "
+ "Full path was '" + path + "'.");
}
//todo change this if instanceof and use DefinitionHandler [lazyman]
if (childDef instanceof EntityDefinition) {
EntityDefinition entityDef = (EntityDefinition) childDef;
if (!entityDef.isEmbedded()) {
//create new criteria
LOGGER.trace("Adding criteria '{}' to context based on sub path\n{}",
new Object[]{entityDef.getJpaName(), propPath.toString()});
addNewCriteriaToContext(propPath, entityDef, entityDef.getJpaName());
} else {
// we don't create new sub criteria, just add this new item path to aliases
addPathAliasToContext(propPath);
}
definition = entityDef;
} else if (childDef instanceof AnyDefinition) {
LOGGER.trace("Adding criteria '{}' to context based on sub path\n{}",
new Object[]{childDef.getJpaName(), propPath.toString()});
addNewCriteriaToContext(propPath, childDef, childDef.getJpaName());
break;
} else if (childDef instanceof CollectionDefinition) {
LOGGER.trace("Adding criteria '{}' to context based on sub path\n{}",
new Object[]{childDef.getJpaName(), propPath.toString()});
addNewCriteriaToContext(propPath, childDef, childDef.getJpaName());
Definition def = ((CollectionDefinition) childDef).getDefinition();
if (def instanceof EntityDefinition) {
definition = (EntityDefinition) def;
}
} else if (childDef instanceof PropertyDefinition || childDef instanceof ReferenceDefinition) {
break;
} else {
//todo throw something here [lazyman]
throw new QueryException("Not implemented yet.");
}
}
}
/**
* This method scans {@link ItemPath} in {@link ValueFilter} and looks for virtual properties, collections
* or entities in {@link QueryDefinitionRegistry}.
* <p/>
* Virtual definitions offer additional query params, which can be used for filtering - this method updates
* criteria based on {@link VirtualQueryParam}. For example assignments and inducements are defined in two
* collections in schema ({@link com.evolveum.midpoint.xml.ns._public.common.common_3.AbstractRoleType}),
* but in repository ({@link com.evolveum.midpoint.repo.sql.data.common.RAbstractRole}) they are stored in
* single {@link java.util.Set}.
* <p/>
* TODO: implement definition handlers to get rid of these methods with many instanceOf comparisons.
*
* @param path
* @return {@link Criterion} based on {@link VirtualQueryParam}
* @throws QueryException
*/
private Criterion createVirtualCriterion(ItemPath path) throws QueryException {
LOGGER.trace("Scanning path for virtual definitions to create criteria {}", new Object[]{path.toString()});
EntityDefinition definition = findProperEntityDefinition(path);
List<Criterion> criterions = new ArrayList<Criterion>();
List<ItemPathSegment> segments = path.getSegments();
List<ItemPathSegment> propPathSegments = new ArrayList<ItemPathSegment>();
ItemPath propPath;
for (ItemPathSegment segment : segments) {
QName qname = ItemPath.getName(segment);
if (ObjectType.F_METADATA.equals(qname)) {
continue;
}
// ugly hack: construction/resourceRef -> resourceRef
if (QNameUtil.match(AssignmentType.F_CONSTRUCTION, qname)) {
continue;
}
// create new property path
propPathSegments.add(new NameItemPathSegment(qname));
propPath = new ItemPath(propPathSegments);
if (QNameUtil.match(qname, ObjectType.F_EXTENSION) || QNameUtil.match(qname, ShadowType.F_ATTRIBUTES)) {
break;
}
// get entity query definition
Definition childDef = definition.findDefinition(qname, Definition.class);
if (childDef == null) {
throw new QueryException("Definition '" + definition + "' doesn't contain child definition '"
+ qname + "'. Please check your path in query, or query entity/attribute mappings. "
+ "Full path was '" + path + "'.");
}
//todo change this if instanceof and use DefinitionHandler [lazyman]
if (childDef instanceof EntityDefinition) {
definition = (EntityDefinition) childDef;
} else if (childDef instanceof CollectionDefinition) {
CollectionDefinition collection = (CollectionDefinition) childDef;
if (childDef instanceof VirtualCollectionDefinition) {
VirtualCollectionDefinition virtual = (VirtualCollectionDefinition) childDef;
criterions.add(updateMainCriterionQueryParam(virtual.getAdditionalParams(), propPath));
}
Definition def = collection.getDefinition();
if (def instanceof EntityDefinition) {
definition = (EntityDefinition) def;
}
} else if (childDef instanceof PropertyDefinition || childDef instanceof ReferenceDefinition
|| childDef instanceof AnyDefinition) {
break;
} else {
//todo throw something here [lazyman]
throw new QueryException("Not implemented yet.");
}
}
return andCriterions(criterions);
}
private Criterion andCriterions(List<Criterion> criterions) {
switch (criterions.size()) {
case 0:
return null;
case 1:
return criterions.get(0);
default:
return Restrictions.and(criterions.toArray(new Criterion[criterions.size()]));
}
}
private Criterion updateMainCriterionQueryParam(VirtualQueryParam[] params, ItemPath propPath)
throws QueryException {
List<Criterion> criterions = new ArrayList<Criterion>();
String alias = getContext().getAlias(propPath);
for (VirtualQueryParam param : params) {
Criterion criterion = Restrictions.eq(alias + "." + param.name(), createQueryParamValue(param, propPath));
criterions.add(criterion);
}
return andCriterions(criterions);
}
/**
* This method provides transformation from {@link String} value defined in
* {@link com.evolveum.midpoint.repo.sql.query.definition.VirtualQueryParam#value()} to real object. Currently only
* to simple types and enum values.
*
* @param param
* @param propPath
* @return real value
* @throws QueryException
*/
private Object createQueryParamValue(VirtualQueryParam param, ItemPath propPath) throws QueryException {
Class type = param.type();
String value = param.value();
try {
if (type.isPrimitive()) {
return type.getMethod("valueOf", new Class[]{String.class}).invoke(null, new Object[]{value});
}
if (type.isEnum()) {
return Enum.valueOf(type, value);
}
} catch (Exception ex) {
throw new QueryException("Couldn't transform virtual query parameter '"
+ param.name() + "' from String to '" + type + "', reason: " + ex.getMessage(), ex);
}
throw new QueryException("Couldn't transform virtual query parameter '"
+ param.name() + "' from String to '" + type + "', it's not yet implemented.");
}
private void addPathAliasToContext(ItemPath path) {
ItemPath lastPropPath = path.allExceptLast();
if (ItemPath.EMPTY_PATH.equivalent(lastPropPath)) {
lastPropPath = null;
}
String alias = getContext().getAlias(lastPropPath);
getContext().addAlias(path, alias);
}
protected void addNewCriteriaToContext(ItemPath path, Definition def, String realName) {
ItemPath lastPropPath = path.allExceptLast();
if (ItemPath.EMPTY_PATH.equivalent(lastPropPath)) {
lastPropPath = null;
}
// Virtual path is defined for example for virtual collections. {c:role/c:assignment} and {c:role/c:iducement}
// must use the same criteria, therefore {c:role/assigmnents} is also path under which is this criteria saved.
final ItemPath virtualPath = lastPropPath != null ? new ItemPath(lastPropPath, new QName("", realName)) :
new ItemPath(new QName("", realName));
Criteria existing = getContext().getCriteria(path);
if (existing != null) {
return;
}
// If there is already criteria on virtual path, only add new path to aliases and criterias.
Criteria virtualCriteria = getContext().getCriteria(virtualPath);
if (virtualCriteria != null) {
getContext().addAlias(path, virtualCriteria.getAlias());
getContext().addCriteria(path, virtualCriteria);
return;
}
// get parent criteria
Criteria pCriteria = getContext().getCriteria(lastPropPath);
// create new criteria and alias for this relationship
String alias = getContext().addAlias(path, def);
Criteria criteria = pCriteria.createCriteria(realName, alias, JoinType.LEFT_OUTER_JOIN);
getContext().addCriteria(path, criteria);
//also add virtual path to criteria map
getContext().addCriteria(virtualPath, criteria);
}
protected Criterion createCriterion(String propertyName, Object value, ValueFilter filter) throws QueryException {
ItemRestrictionOperation operation;
if (filter instanceof EqualFilter) {
operation = ItemRestrictionOperation.EQ;
} else if (filter instanceof GreaterFilter) {
GreaterFilter gf = (GreaterFilter) filter;
operation = gf.isEquals() ? ItemRestrictionOperation.GE : ItemRestrictionOperation.GT;
} else if (filter instanceof LessFilter) {
LessFilter lf = (LessFilter) filter;
operation = lf.isEquals() ? ItemRestrictionOperation.LE : ItemRestrictionOperation.LT;
} else if (filter instanceof SubstringFilter) {
SubstringFilter substring = (SubstringFilter) filter;
if (substring.isAnchorEnd()) {
operation = ItemRestrictionOperation.ENDS_WITH;
} else if (substring.isAnchorStart()) {
operation = ItemRestrictionOperation.STARTS_WITH;
} else {
operation = ItemRestrictionOperation.SUBSTRING;
}
} else {
throw new QueryException("Can't translate filter '" + filter + "' to operation.");
}
QueryContext context = getContext();
QueryInterpreter interpreter = context.getInterpreter();
Matcher matcher = interpreter.findMatcher(value);
String matchingRule = null;
if (filter.getMatchingRule() != null){
matchingRule = filter.getMatchingRule().getLocalPart();
}
return matcher.match(operation, propertyName, value, matchingRule);
}
protected List<Definition> createDefinitionPath(ItemPath path) throws QueryException {
List<Definition> definitions = new ArrayList<Definition>();
if (path == null) {
return definitions;
}
EntityDefinition lastDefinition = findProperEntityDefinition(path);
for (ItemPathSegment segment : path.getSegments()) {
if (lastDefinition == null) {
break;
}
if (!(segment instanceof NameItemPathSegment)) {
continue;
}
NameItemPathSegment named = (NameItemPathSegment) segment;
Definition def = lastDefinition.findDefinition(named.getName(), Definition.class);
definitions.add(def);
if (def instanceof EntityDefinition) {
lastDefinition = (EntityDefinition) def;
} else if (def instanceof CollectionDefinition) { // todo this seems logical but is it correct? [mederly]
def = ((CollectionDefinition) def).getDefinition();
if (def instanceof EntityDefinition) {
lastDefinition = ((EntityDefinition) def);
} else {
lastDefinition = null;
}
}
}
return definitions;
}
protected Object getValue(List<? extends PrismValue> values) {
if (values == null || values.isEmpty()) {
return null;
}
PrismValue val = values.get(0);
if (val instanceof PrismPropertyValue) {
PrismPropertyValue propertyValue = (PrismPropertyValue) val;
return propertyValue.getValue();
}
return null;
}
protected Object getValueFromFilter(ValueFilter filter, PropertyDefinition def) throws QueryException {
Object value;
if (filter != null) {
value = getValue((filter).getValues());
} else {
throw new QueryException("Unknown filter '" + filter + "', can't get value from it.");
}
//todo remove after some time [lazyman]
//attempt to fix value type for polystring (if it was string in filter we create polystring from it)
if (PolyString.class.equals(def.getJaxbType()) && (value instanceof String)) {
LOGGER.debug("Trying to query PolyString value but filter contains String '{}'.", new Object[]{filter});
value = new PolyString((String) value, (String) value);
}
//attempt to fix value type for polystring (if it was polystringtype in filter we create polystring from it)
if (PolyString.class.equals(def.getJaxbType()) && (value instanceof PolyStringType)) {
LOGGER.debug("Trying to query PolyString value but filter contains PolyStringType '{}'.", new Object[]{filter});
PolyStringType type = (PolyStringType) value;
value = new PolyString(type.getOrig(), type.getNorm());
}
if (String.class.equals(def.getJaxbType()) && (value instanceof QName)) {
//eg. shadow/objectClass
value = RUtil.qnameToString((QName) value);
}
if (value != null && !def.getJaxbType().isAssignableFrom(value.getClass())) {
throw new QueryException("Value should by type of '" + def.getJaxbType() + "' but it's '"
+ value.getClass() + "', filter '" + filter + "'.");
}
if (def.isEnumerated()) {
value = getRepoEnumValue((Enum) value, def.getJpaType());
}
return value;
}
private Enum getRepoEnumValue(Enum schemaValue, Class repoType) throws QueryException {
if (schemaValue == null) {
return null;
}
if (SchemaEnum.class.isAssignableFrom(repoType)) {
return (Enum) RUtil.getRepoEnumValue(schemaValue, repoType);
}
Object[] constants = repoType.getEnumConstants();
for (Object constant : constants) {
Enum e = (Enum) constant;
if (e.name().equals(schemaValue.name())) {
return e;
}
}
throw new QueryException("Unknown enum value '" + schemaValue + "', which is type of '"
+ schemaValue.getClass() + "'.");
}
protected String createPropertyOrReferenceNamePrefix(ItemPath path) throws QueryException {
StringBuilder sb = new StringBuilder();
EntityDefinition definition = findProperEntityDefinition(path);
List<ItemPathSegment> segments = path.getSegments();
for (ItemPathSegment segment : segments) {
QName qname = ItemPath.getName(segment);
if (ObjectType.F_METADATA.equals(qname)) { // todo not QNameUtil.match? [mederly]
continue;
}
if (QNameUtil.match(AssignmentType.F_CONSTRUCTION, qname)) { // ugly hack: construction/resourceRef -> resourceRef
continue;
}
// get entity query definition
Definition childDef = definition.findDefinition(qname, Definition.class);
//todo change this if instanceof and use DefinitionHandler [lazyman]
if (childDef instanceof EntityDefinition) {
EntityDefinition entityDef = (EntityDefinition) childDef;
if (entityDef.isEmbedded()) {
// we don't create new sub criteria, just add dot with jpaName
sb.append(entityDef.getJpaName());
sb.append('.');
}
definition = entityDef;
} else if (childDef instanceof CollectionDefinition) {
Definition def = ((CollectionDefinition) childDef).getDefinition();
if (def instanceof EntityDefinition) {
definition = (EntityDefinition) def;
}
} else if (childDef instanceof PropertyDefinition || childDef instanceof ReferenceDefinition) {
break;
} else {
throw new QueryException("Not implemented yet. Create property name prefix for segment '"
+ segment + "', path '" + path + "'.");
}
}
return sb.toString();
}
}