View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.syncope.core.persistence.api.search;
20  
21  import java.net.URLDecoder;
22  import java.nio.charset.StandardCharsets;
23  import java.util.ArrayList;
24  import java.util.List;
25  import java.util.Optional;
26  import java.util.regex.Pattern;
27  import org.apache.cxf.jaxrs.ext.search.ConditionType;
28  import org.apache.cxf.jaxrs.ext.search.SearchBean;
29  import org.apache.cxf.jaxrs.ext.search.SearchCondition;
30  import org.apache.cxf.jaxrs.ext.search.SearchUtils;
31  import org.apache.cxf.jaxrs.ext.search.visitor.AbstractSearchConditionVisitor;
32  import org.apache.syncope.common.lib.search.SearchableFields;
33  import org.apache.syncope.common.lib.search.SpecialAttr;
34  import org.apache.syncope.common.lib.search.SyncopeFiqlParser;
35  import org.apache.syncope.common.lib.search.SyncopeFiqlSearchCondition;
36  import org.apache.syncope.core.persistence.api.dao.search.AnyCond;
37  import org.apache.syncope.core.persistence.api.dao.search.AnyTypeCond;
38  import org.apache.syncope.core.persistence.api.dao.search.AttrCond;
39  import org.apache.syncope.core.persistence.api.dao.search.AuxClassCond;
40  import org.apache.syncope.core.persistence.api.dao.search.DynRealmCond;
41  import org.apache.syncope.core.persistence.api.dao.search.MemberCond;
42  import org.apache.syncope.core.persistence.api.dao.search.MembershipCond;
43  import org.apache.syncope.core.persistence.api.dao.search.PrivilegeCond;
44  import org.apache.syncope.core.persistence.api.dao.search.RelationshipCond;
45  import org.apache.syncope.core.persistence.api.dao.search.RelationshipTypeCond;
46  import org.apache.syncope.core.persistence.api.dao.search.ResourceCond;
47  import org.apache.syncope.core.persistence.api.dao.search.RoleCond;
48  import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
49  
50  /**
51   * Visits CXF's {@link SearchBean} and produces {@link SearchCond}.
52   */
53  public class SearchCondVisitor extends AbstractSearchConditionVisitor<SearchBean, SearchCond> {
54  
55      protected static final Pattern TIMEZONE = Pattern.compile(".* [0-9]{4}$");
56  
57      protected static final ThreadLocal<String> REALM = new ThreadLocal<>();
58  
59      protected static final ThreadLocal<SearchCond> SEARCH_COND = new ThreadLocal<>();
60  
61      public SearchCondVisitor() {
62          super(null);
63      }
64  
65      public void setRealm(final String realm) {
66          REALM.set(realm);
67      }
68  
69      protected static AttrCond createAttrCond(final String schema) {
70          AttrCond attrCond = SearchableFields.contains(schema)
71                  ? new AnyCond()
72                  : new AttrCond();
73          attrCond.setSchema(schema);
74          return attrCond;
75      }
76  
77      protected static String getValue(final SearchCondition<SearchBean> sc) {
78          String value = SearchUtils.toSqlWildcardString(
79                  URLDecoder.decode(sc.getStatement().getValue().toString(), StandardCharsets.UTF_8), false);
80          if (value.indexOf('%') == -1) {
81              value = value.replaceAll("\\\\_", "_");
82          }
83  
84          if (TIMEZONE.matcher(value).matches()) {
85              char[] valueAsArray = value.toCharArray();
86              valueAsArray[valueAsArray.length - 5] = '+';
87              value = new String(valueAsArray);
88          }
89  
90          return value;
91      }
92  
93      protected static ConditionType getConditionType(final SearchCondition<SearchBean> sc) {
94          ConditionType ct = sc.getConditionType();
95          if (sc instanceof SyncopeFiqlSearchCondition && sc.getConditionType() == ConditionType.CUSTOM) {
96              SyncopeFiqlSearchCondition<SearchBean> sfsc = (SyncopeFiqlSearchCondition<SearchBean>) sc;
97              switch (sfsc.getOperator()) {
98                  case SyncopeFiqlParser.IEQ:
99                      ct = ConditionType.EQUALS;
100                     break;
101 
102                 case SyncopeFiqlParser.NIEQ:
103                     ct = ConditionType.NOT_EQUALS;
104                     break;
105 
106                 default:
107                     throw new IllegalArgumentException(
108                             String.format("Condition type %s is not supported", sfsc.getOperator()));
109             }
110         }
111 
112         return ct;
113     }
114 
115     @SuppressWarnings("ConvertToStringSwitch")
116     protected SearchCond visitPrimitive(final SearchCondition<SearchBean> sc) {
117         String name = getRealPropertyName(sc.getStatement().getProperty());
118         Optional<SpecialAttr> specialAttrName = SpecialAttr.fromString(name);
119 
120         String value = getValue(sc);
121         Optional<SpecialAttr> specialAttrValue = SpecialAttr.fromString(value);
122 
123         AttrCond attrCond = createAttrCond(name);
124         attrCond.setExpression(value);
125 
126         ConditionType ct = getConditionType(sc);
127 
128         SearchCond leaf;
129         switch (ct) {
130             case EQUALS:
131             case NOT_EQUALS:
132                 if (specialAttrName.isEmpty()) {
133                     if (specialAttrValue.isPresent() && specialAttrValue.get() == SpecialAttr.NULL) {
134                         attrCond.setType(AttrCond.Type.ISNULL);
135                         attrCond.setExpression(null);
136                     } else if (value.indexOf('%') == -1) {
137                         attrCond.setType(sc.getConditionType() == ConditionType.CUSTOM
138                                 ? AttrCond.Type.IEQ
139                                 : AttrCond.Type.EQ);
140                     } else {
141                         attrCond.setType(sc.getConditionType() == ConditionType.CUSTOM
142                                 ? AttrCond.Type.ILIKE
143                                 : AttrCond.Type.LIKE);
144                     }
145 
146                     leaf = SearchCond.getLeaf(attrCond);
147                 } else {
148                     switch (specialAttrName.get()) {
149                         case TYPE:
150                             AnyTypeCond typeCond = new AnyTypeCond();
151                             typeCond.setAnyTypeKey(value);
152                             leaf = SearchCond.getLeaf(typeCond);
153                             break;
154 
155                         case AUX_CLASSES:
156                             AuxClassCond auxClassCond = new AuxClassCond();
157                             auxClassCond.setAuxClass(value);
158                             leaf = SearchCond.getLeaf(auxClassCond);
159                             break;
160 
161                         case RESOURCES:
162                             ResourceCond resourceCond = new ResourceCond();
163                             resourceCond.setResource(value);
164                             leaf = SearchCond.getLeaf(resourceCond);
165                             break;
166 
167                         case GROUPS:
168                             MembershipCond groupCond = new MembershipCond();
169                             groupCond.setGroup(value);
170                             leaf = SearchCond.getLeaf(groupCond);
171                             break;
172 
173                         case RELATIONSHIPS:
174                             RelationshipCond relationshipCond = new RelationshipCond();
175                             relationshipCond.setAnyObject(value);
176                             leaf = SearchCond.getLeaf(relationshipCond);
177                             break;
178 
179                         case RELATIONSHIP_TYPES:
180                             RelationshipTypeCond relationshipTypeCond = new RelationshipTypeCond();
181                             relationshipTypeCond.setRelationshipTypeKey(value);
182                             leaf = SearchCond.getLeaf(relationshipTypeCond);
183                             break;
184 
185                         case ROLES:
186                             RoleCond roleCond = new RoleCond();
187                             roleCond.setRole(value);
188                             leaf = SearchCond.getLeaf(roleCond);
189                             break;
190 
191                         case PRIVILEGES:
192                             PrivilegeCond privilegeCond = new PrivilegeCond();
193                             privilegeCond.setPrivilege(value);
194                             leaf = SearchCond.getLeaf(privilegeCond);
195                             break;
196 
197                         case DYNREALMS:
198                             DynRealmCond dynRealmCond = new DynRealmCond();
199                             dynRealmCond.setDynRealm(value);
200                             leaf = SearchCond.getLeaf(dynRealmCond);
201                             break;
202 
203                         case MEMBER:
204                             MemberCond memberCond = new MemberCond();
205                             memberCond.setMember(value);
206                             leaf = SearchCond.getLeaf(memberCond);
207                             break;
208 
209                         default:
210                             throw new IllegalArgumentException(
211                                     String.format("Special attr name %s is not supported", specialAttrName));
212                     }
213                 }
214                 if (ct == ConditionType.NOT_EQUALS) {
215                     Optional<AttrCond> notEquals = leaf.getLeaf(AttrCond.class);
216                     if (notEquals.isPresent() && notEquals.get().getType() == AttrCond.Type.ISNULL) {
217                         notEquals.get().setType(AttrCond.Type.ISNOTNULL);
218                     } else {
219                         leaf = SearchCond.getNotLeaf(leaf);
220                     }
221                 }
222                 break;
223 
224             case GREATER_OR_EQUALS:
225                 attrCond.setType(AttrCond.Type.GE);
226                 leaf = SearchCond.getLeaf(attrCond);
227                 break;
228 
229             case GREATER_THAN:
230                 attrCond.setType(AttrCond.Type.GT);
231                 leaf = SearchCond.getLeaf(attrCond);
232                 break;
233 
234             case LESS_OR_EQUALS:
235                 attrCond.setType(AttrCond.Type.LE);
236                 leaf = SearchCond.getLeaf(attrCond);
237                 break;
238 
239             case LESS_THAN:
240                 attrCond.setType(AttrCond.Type.LT);
241                 leaf = SearchCond.getLeaf(attrCond);
242                 break;
243 
244             default:
245                 throw new IllegalArgumentException(String.format("Condition type %s is not supported", ct.name()));
246         }
247 
248         // SYNCOPE-1293: explicitly re-process to allow 'token==$null' or 'token!=$null'
249         Optional<AttrCond> reprocess = leaf.getLeaf(AttrCond.class).
250                 filter(cond -> "token".equals(cond.getSchema())
251                 && (cond.getType() == AttrCond.Type.ISNULL || cond.getType() == AttrCond.Type.ISNOTNULL)
252                 && cond.getExpression() == null);
253         if (reprocess.isPresent()) {
254             AnyCond tokenCond = new AnyCond();
255             tokenCond.setSchema(reprocess.get().getSchema());
256             tokenCond.setType(reprocess.get().getType());
257             tokenCond.setExpression(null);
258             leaf = SearchCond.getLeaf(tokenCond);
259         }
260 
261         return leaf;
262     }
263 
264     protected SearchCond visitCompound(final SearchCondition<SearchBean> sc) {
265         List<SearchCond> searchConds = new ArrayList<>();
266         sc.getSearchConditions().forEach(searchCond -> {
267             searchConds.add(searchCond.getStatement() == null
268                     ? visitCompound(searchCond)
269                     : visitPrimitive(searchCond));
270         });
271 
272         SearchCond compound;
273         switch (sc.getConditionType()) {
274             case AND:
275                 compound = SearchCond.getAnd(searchConds);
276                 break;
277 
278             case OR:
279                 compound = SearchCond.getOr(searchConds);
280                 break;
281 
282             default:
283                 throw new IllegalArgumentException(
284                         String.format("Condition type %s is not supported", sc.getConditionType().name()));
285         }
286 
287         return compound;
288     }
289 
290     @Override
291     public void visit(final SearchCondition<SearchBean> sc) {
292         SEARCH_COND.set(sc.getStatement() == null ? visitCompound(sc) : visitPrimitive(sc));
293     }
294 
295     @Override
296     public SearchCond getQuery() {
297         return SEARCH_COND.get();
298     }
299 }