| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 org.apache.syncope.core.persistence.jpa.dao; |
| |
| import java.util.List; |
| import java.util.Optional; |
| import java.util.Set; |
| import java.util.stream.Collectors; |
| import org.apache.commons.lang3.tuple.Pair; |
| import org.apache.syncope.common.lib.types.AttrSchemaType; |
| import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO; |
| import org.apache.syncope.core.persistence.api.dao.DynRealmDAO; |
| import org.apache.syncope.core.persistence.api.dao.GroupDAO; |
| import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO; |
| import org.apache.syncope.core.persistence.api.dao.RealmDAO; |
| import org.apache.syncope.core.persistence.api.dao.UserDAO; |
| import org.apache.syncope.core.persistence.api.dao.search.AnyCond; |
| import org.apache.syncope.core.persistence.api.dao.search.AttrCond; |
| import org.apache.syncope.core.persistence.api.dao.search.OrderByClause; |
| import org.apache.syncope.core.persistence.api.entity.AnyUtils; |
| import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory; |
| import org.apache.syncope.core.persistence.api.entity.EntityFactory; |
| import org.apache.syncope.core.persistence.api.entity.PlainAttr; |
| import org.apache.syncope.core.persistence.api.entity.PlainAttrUniqueValue; |
| import org.apache.syncope.core.persistence.api.entity.PlainAttrValue; |
| import org.apache.syncope.core.persistence.api.entity.PlainSchema; |
| import org.apache.syncope.core.provisioning.api.serialization.POJOHelper; |
| import org.apache.syncope.core.persistence.api.entity.JSONPlainAttr; |
| |
| public class MyJPAJSONAnySearchDAO extends JPAAnySearchDAO { |
| |
| public MyJPAJSONAnySearchDAO( |
| final RealmDAO realmDAO, |
| final DynRealmDAO dynRealmDAO, |
| final UserDAO userDAO, |
| final GroupDAO groupDAO, |
| final AnyObjectDAO anyObjectDAO, |
| final PlainSchemaDAO schemaDAO, |
| final EntityFactory entityFactory, |
| final AnyUtilsFactory anyUtilsFactory) { |
| |
| super(realmDAO, dynRealmDAO, userDAO, groupDAO, anyObjectDAO, schemaDAO, entityFactory, anyUtilsFactory); |
| } |
| |
| @Override |
| protected void processOBS( |
| final SearchSupport svs, |
| final OrderBySupport obs, |
| final StringBuilder where) { |
| |
| Set<String> attrs = obs.items.stream(). |
| map(item -> item.orderBy.substring(0, item.orderBy.indexOf(" "))).collect(Collectors.toSet()); |
| |
| obs.views.forEach(searchView -> { |
| if (searchView.name.equals(svs.field().name)) { |
| StringBuilder attrWhere = new StringBuilder(); |
| StringBuilder nullAttrWhere = new StringBuilder(); |
| |
| where.append(", (SELECT * FROM ").append(searchView.name); |
| |
| if (svs.nonMandatorySchemas || obs.nonMandatorySchemas) { |
| attrs.forEach(field -> { |
| if (attrWhere.length() == 0) { |
| attrWhere.append(" WHERE "); |
| } else { |
| attrWhere.append(" OR "); |
| } |
| attrWhere.append("JSON_CONTAINS(plainAttrs, '[{\"schema\":\"").append(field).append("\"}]')"); |
| |
| nullAttrWhere.append(" UNION SELECT DISTINCT any_id,").append(svs.table().alias).append(".*, "). |
| append('"').append(field).append('"').append(" AS plainShema, "). |
| append("null AS binaryValue, "). |
| append("null AS booleanValue, "). |
| append("null AS dateValue, "). |
| append("null AS doubleValue, "). |
| append("null AS longValue, "). |
| append("null AS stringValue, "). |
| append("null AS attrUniqueValue"). |
| append(" FROM ").append(svs.table().name).append(' ').append(svs.table().alias). |
| append(", ").append(svs.field().name). |
| append(" WHERE any_id=").append(svs.table().alias).append(".id"). |
| append(" AND any_id NOT IN "). |
| append("(SELECT distinct any_id FROM "). |
| append(svs.field().name). |
| append(" WHERE ").append(svs.table().alias).append(".id=any_id AND "). |
| append("JSON_CONTAINS(plainAttrs, '[{\"schema\":\"").append(field).append("\"}]'))"); |
| }); |
| where.append(attrWhere).append(nullAttrWhere); |
| } |
| |
| where.append(')'); |
| } else { |
| where.append(',').append(searchView.name); |
| } |
| where.append(' ').append(searchView.alias); |
| }); |
| } |
| |
| @Override |
| protected void parseOrderByForPlainSchema( |
| final SearchSupport svs, |
| final OrderBySupport obs, |
| final OrderBySupport.Item item, |
| final OrderByClause clause, |
| final PlainSchema schema, |
| final String fieldName) { |
| |
| // keep track of involvement of non-mandatory schemas in the order by clauses |
| obs.nonMandatorySchemas = !"true".equals(schema.getMandatoryCondition()); |
| |
| obs.views.add(svs.field()); |
| |
| item.select = svs.field().alias + '.' |
| + (schema.isUniqueConstraint() ? "attrUniqueValue" : key(schema.getType())) |
| + " AS " + fieldName; |
| item.where = "plainSchema = '" + fieldName + '\''; |
| item.orderBy = fieldName + ' ' + clause.getDirection().name(); |
| } |
| |
| protected void fillAttrQuery( |
| final AnyUtils anyUtils, |
| final StringBuilder query, |
| final PlainAttrValue attrValue, |
| final PlainSchema schema, |
| final AttrCond cond, |
| final boolean not, |
| final List<Object> parameters, |
| final SearchSupport svs) { |
| |
| // This first branch is required for handling with not conditions given on multivalue fields (SYNCOPE-1419) |
| if (not && schema.isMultivalue() |
| && !(cond instanceof AnyCond) |
| && cond.getType() != AttrCond.Type.ISNULL && cond.getType() != AttrCond.Type.ISNOTNULL) { |
| |
| query.append("id NOT IN (SELECT DISTINCT any_id FROM "); |
| query.append(svs.field().name).append(" WHERE "); |
| fillAttrQuery(anyUtils, query, attrValue, schema, cond, false, parameters, svs); |
| query.append(')'); |
| } else { |
| if (!not && cond.getType() == AttrCond.Type.EQ) { |
| PlainAttr<?> container = anyUtils.newPlainAttr(); |
| container.setSchema(schema); |
| if (attrValue instanceof PlainAttrUniqueValue) { |
| container.setUniqueValue((PlainAttrUniqueValue) attrValue); |
| } else { |
| ((JSONPlainAttr) container).add(attrValue); |
| } |
| |
| query.append("JSON_CONTAINS(plainAttrs, '"). |
| append(POJOHelper.serialize(List.of(container)).replace("'", "''")). |
| append("')"); |
| } else { |
| String key = key(schema.getType()); |
| |
| String value = Optional.ofNullable(attrValue.getDateValue()). |
| map(v -> String.valueOf(v.getTime())). |
| orElse(cond.getExpression()); |
| |
| boolean lower = (schema.getType() == AttrSchemaType.String || schema.getType() == AttrSchemaType.Enum) |
| && (cond.getType() == AttrCond.Type.IEQ || cond.getType() == AttrCond.Type.ILIKE); |
| |
| query.append("plainSchema = ?").append(setParameter(parameters, cond.getSchema())). |
| append(" AND "). |
| append(lower ? "LOWER(" : ""). |
| append(schema.isUniqueConstraint() |
| ? "attrUniqueValue ->> '$." + key + '\'' |
| : key). |
| append(lower ? ')' : ""); |
| |
| switch (cond.getType()) { |
| case LIKE: |
| case ILIKE: |
| if (not) { |
| query.append("NOT "); |
| } |
| query.append(" LIKE "); |
| break; |
| |
| case GE: |
| if (not) { |
| query.append('<'); |
| } else { |
| query.append(">="); |
| } |
| break; |
| |
| case GT: |
| if (not) { |
| query.append("<="); |
| } else { |
| query.append('>'); |
| } |
| break; |
| |
| case LE: |
| if (not) { |
| query.append('>'); |
| } else { |
| query.append("<="); |
| } |
| break; |
| |
| case LT: |
| if (not) { |
| query.append(">="); |
| } else { |
| query.append('<'); |
| } |
| break; |
| |
| case EQ: |
| case IEQ: |
| default: |
| if (not) { |
| query.append('!'); |
| } |
| query.append('='); |
| } |
| |
| query.append(lower ? "LOWER(" : ""). |
| append('?').append(setParameter(parameters, value)). |
| append(lower ? ")" : ""); |
| } |
| } |
| } |
| |
| @Override |
| protected String getQuery( |
| final AttrCond cond, |
| final boolean not, |
| final List<Object> parameters, |
| final SearchSupport svs) { |
| |
| Pair<PlainSchema, PlainAttrValue> checked; |
| try { |
| checked = check(cond, svs.anyTypeKind); |
| } catch (IllegalArgumentException e) { |
| return EMPTY_QUERY; |
| } |
| |
| // normalize NULL / NOT NULL checks |
| if (not) { |
| if (cond.getType() == AttrCond.Type.ISNULL) { |
| cond.setType(AttrCond.Type.ISNOTNULL); |
| } else if (cond.getType() == AttrCond.Type.ISNOTNULL) { |
| cond.setType(AttrCond.Type.ISNULL); |
| } |
| } |
| |
| StringBuilder query = |
| new StringBuilder("SELECT DISTINCT any_id FROM ").append(svs.field().name).append(" WHERE "); |
| switch (cond.getType()) { |
| case ISNOTNULL: |
| query.append("JSON_SEARCH(plainAttrs, 'one', '"). |
| append(checked.getLeft().getKey()). |
| append("', NULL, '$[*].schema') IS NOT NULL"); |
| break; |
| |
| case ISNULL: |
| query.append("JSON_SEARCH(plainAttrs, 'one', '"). |
| append(checked.getLeft().getKey()). |
| append("', NULL, '$[*].schema') IS NULL"); |
| break; |
| |
| default: |
| if (not && !(cond instanceof AnyCond) && checked.getLeft().isMultivalue()) { |
| query = new StringBuilder("SELECT DISTINCT id AS any_id FROM ").append(svs.table().name). |
| append(" WHERE "); |
| } |
| fillAttrQuery(anyUtilsFactory.getInstance(svs.anyTypeKind), |
| query, checked.getRight(), checked.getLeft(), cond, not, parameters, svs); |
| } |
| |
| return query.toString(); |
| } |
| } |