1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 }