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.ext.scimv2.api.data;
20  
21  import com.fasterxml.jackson.core.JsonParser;
22  import com.fasterxml.jackson.core.JsonProcessingException;
23  import com.fasterxml.jackson.databind.DeserializationContext;
24  import com.fasterxml.jackson.databind.JsonNode;
25  import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
26  import java.io.IOException;
27  import java.io.Serializable;
28  import java.util.List;
29  import java.util.Optional;
30  import java.util.regex.Matcher;
31  import java.util.regex.Pattern;
32  import org.apache.cxf.common.util.StringUtils;
33  import org.apache.syncope.ext.scimv2.api.type.PatchOp;
34  
35  public class SCIMPatchOperationDeserializer extends StdDeserializer<SCIMPatchOperation> {
36  
37      private static final long serialVersionUID = -7401353969242788372L;
38  
39      private static final Pattern PATH_PATTERN = Pattern.compile(
40              "^(?<schema>[A-Za-z0-9:.]+:)?(?<attribute>\\w+)(?<filter>\\[.*\\])?(?<sub>\\.\\w+)?");
41  
42      private static Serializable scalar(final JsonNode v) {
43          if (v.isNull()) {
44              return null;
45          }
46          if (v.isBoolean()) {
47              return v.booleanValue();
48          }
49          if (v.isFloat()) {
50              return v.floatValue();
51          }
52          if (v.isDouble()) {
53              return v.doubleValue();
54          }
55          if (v.isInt()) {
56              return v.intValue();
57          }
58          if (v.isShort()) {
59              return v.shortValue();
60          }
61          if (v.isLong()) {
62              return v.longValue();
63          }
64  
65          return v.asText();
66      }
67  
68      public SCIMPatchOperationDeserializer() {
69          this(null);
70      }
71  
72      public SCIMPatchOperationDeserializer(final Class<?> vc) {
73          super(vc);
74      }
75  
76      @Override
77      public SCIMPatchOperation deserialize(final JsonParser jp, final DeserializationContext ctxt)
78              throws IOException, JsonProcessingException {
79  
80          JsonNode node = jp.getCodec().readTree(jp);
81  
82          SCIMPatchOperation scimPatchOperation = new SCIMPatchOperation();
83  
84          if (node.has("op")) {
85              scimPatchOperation.setOp(PatchOp.valueOf(node.get("op").asText().toLowerCase()));
86          }
87  
88          if (node.has("path")) {
89              Matcher matcher = PATH_PATTERN.matcher(node.get("path").asText());
90              if (matcher.matches()) {
91                  SCIMPatchPath path = new SCIMPatchPath();
92                  scimPatchOperation.setPath(path);
93  
94                  Optional.ofNullable(matcher.group("schema")).
95                          ifPresent(schema -> path.setSchema(schema.substring(0, schema.length() - 1)));
96  
97                  path.setAttribute(StringUtils.uncapitalize(matcher.group("attribute")));
98  
99                  Optional.ofNullable(matcher.group("filter")).
100                         ifPresent(condition -> path.setFilter(condition.substring(1, condition.length() - 1)));
101 
102                 Optional.ofNullable(matcher.group("sub")).
103                         ifPresent(sub -> path.setSub(StringUtils.uncapitalize(sub.substring(1))));
104             }
105         }
106 
107         if (node.has("value")) {
108             JsonNode value = node.get("value");
109 
110             if (scimPatchOperation.getPath() == null) {
111                 scimPatchOperation.setValue(List.of(jp.getCodec().treeToValue(value, SCIMUser.class)));
112             } else {
113                 if ("members".equals(scimPatchOperation.getPath().getAttribute())) {
114                     scimPatchOperation.setValue(List.of(
115                             (Serializable[]) jp.getCodec().treeToValue(value, Member[].class)));
116                 } else if (value.isObject()) {
117                     SCIMUser user = new SCIMUser(
118                             null,
119                             List.of(),
120                             null,
121                             "userName".equals(scimPatchOperation.getPath().getAttribute()) ? value.asText() : null,
122                             "active".equals(scimPatchOperation.getPath().getAttribute()) ? value.asBoolean() : null);
123                     user.setEnterpriseInfo(new SCIMEnterpriseInfo());
124 
125                     switch (scimPatchOperation.getPath().getAttribute()) {
126                         case "externalId":
127                             user.setExternalId(value.asText());
128                             break;
129 
130                         case "name":
131                             user.setName(jp.getCodec().treeToValue(value, SCIMUserName.class));
132                             break;
133 
134                         case "displayName":
135                             user.setDisplayName(value.asText());
136                             break;
137 
138                         case "nickName":
139                             user.setNickName(value.asText());
140                             break;
141 
142                         case "profileUrl":
143                             user.setProfileUrl(value.asText());
144                             break;
145 
146                         case "title":
147                             user.setTitle(value.asText());
148                             break;
149 
150                         case "userType":
151                             user.setUserType(value.asText());
152                             break;
153 
154                         case "preferredLanguage":
155                             user.setPreferredLanguage(value.asText());
156                             break;
157 
158                         case "locale":
159                             user.setLocale(value.asText());
160                             break;
161 
162                         case "timezone":
163                             user.setTimezone(value.asText());
164                             break;
165 
166                         case "emails":
167                             user.getEmails().add(jp.getCodec().treeToValue(value, SCIMComplexValue.class));
168                             break;
169 
170                         case "phoneNumbers":
171                             user.getPhoneNumbers().add(jp.getCodec().treeToValue(value, SCIMComplexValue.class));
172                             break;
173 
174                         case "ims":
175                             user.getIms().add(jp.getCodec().treeToValue(value, SCIMComplexValue.class));
176                             break;
177 
178                         case "photos":
179                             user.getPhotos().add(jp.getCodec().treeToValue(value, SCIMComplexValue.class));
180                             break;
181 
182                         case "addresses":
183                             user.getAddresses().add(jp.getCodec().treeToValue(value, SCIMUserAddress.class));
184                             break;
185 
186                         case "x509Certificates":
187                             user.getX509Certificates().add(jp.getCodec().treeToValue(value, Value.class));
188                             break;
189 
190                         case "employeeNumber":
191                             user.getEnterpriseInfo().setEmployeeNumber(value.asText());
192                             break;
193 
194                         case "costCenter":
195                             user.getEnterpriseInfo().setCostCenter(value.asText());
196                             break;
197 
198                         case "organization":
199                             user.getEnterpriseInfo().setOrganization(value.asText());
200                             break;
201 
202                         case "division":
203                             user.getEnterpriseInfo().setDivision(value.asText());
204                             break;
205 
206                         case "department":
207                             user.getEnterpriseInfo().setDepartment(value.asText());
208                             break;
209 
210                         case "manager":
211                             user.getEnterpriseInfo().
212                                     setManager(jp.getCodec().treeToValue(value, SCIMUserManager.class));
213                             break;
214 
215                         default:
216                     }
217 
218                     scimPatchOperation.setValue(List.of(user));
219                 } else if (!value.isContainerNode()) {
220                     scimPatchOperation.setValue(List.of(scalar(value)));
221                 }
222             }
223         }
224 
225         return scimPatchOperation;
226     }
227 }