001 /*
002 * Copyright 2011-2012 UnboundID Corp.
003 *
004 * This program is free software; you can redistribute it and/or modify
005 * it under the terms of the GNU General Public License (GPLv2 only)
006 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
007 * as published by the Free Software Foundation.
008 *
009 * This program is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
012 * GNU General Public License for more details.
013 *
014 * You should have received a copy of the GNU General Public License
015 * along with this program; if not, see <http://www.gnu.org/licenses>.
016 */
017
018 package com.unboundid.scim.schema;
019
020 import com.unboundid.scim.data.AttributeValueResolver;
021 import com.unboundid.scim.data.Entry;
022 import com.unboundid.scim.sdk.InvalidResourceException;
023 import com.unboundid.scim.sdk.SCIMAttribute;
024 import com.unboundid.scim.sdk.SCIMAttributeValue;
025 import com.unboundid.scim.sdk.SCIMConstants;
026
027 import java.util.ArrayList;
028 import java.util.Arrays;
029 import java.util.Collection;
030 import java.util.LinkedHashMap;
031 import java.util.List;
032 import java.util.Map;
033
034 import static com.unboundid.scim.sdk.StaticUtils.toLowerCase;
035
036
037
038 /**
039 * This class provides methods that describe the schema for a SCIM attribute.
040 * It may be used to help read and write SCIM attributes in their external XML
041 * and JSON representation, and to convert SCIM attributes to and from LDAP
042 * attributes.
043 */
044 public final class AttributeDescriptor {
045
046 /**
047 * Defines the set of well known SCIM supported datatypes.
048 */
049 public static enum DataType {
050 /**
051 * String data type.
052 */
053 STRING,
054 /**
055 * Boolean data type.
056 */
057 BOOLEAN,
058 /**
059 * Date Time data type.
060 */
061 DATETIME,
062 /**
063 * Decimal data type.
064 */
065 DECIMAL,
066 /**
067 * Integer data type.
068 */
069 INTEGER,
070 /**
071 * Binary data type.
072 */
073 BINARY,
074 /**
075 * Complex data type.
076 */
077 COMPLEX;
078
079 /**
080 * Parses a supplied data type into a SCIM defined data type.
081 * @param type The type to convert
082 *
083 * @return The DataType or null if not supported
084 */
085 public static DataType parse(final String type) {
086 try {
087 return DataType.valueOf(type.toUpperCase());
088 } catch (Exception e) {
089 return null;
090 }
091 }
092
093 /**
094 * {@inheritDoc}
095 */
096 @Override
097 public String toString() {
098 return super.toString().toLowerCase();
099 }
100 }
101
102 static final class SubAttributeDescriptorResolver
103 extends AttributeValueResolver<AttributeDescriptor>
104 {
105 private final String schema;
106
107 /**
108 * Construct a new sub attribute resolver.
109 *
110 * @param schema The schema of the parent attribute.
111 */
112 SubAttributeDescriptorResolver(final String schema) {
113 this.schema = schema;
114 }
115
116 /**
117 * {@inheritDoc}
118 */
119 @Override
120 public SCIMAttributeValue fromInstance(
121 final AttributeDescriptor attributeDescriptor,
122 final AttributeDescriptor value) throws InvalidResourceException {
123 final List<SCIMAttribute> attributes =
124 new ArrayList<SCIMAttribute>(6);
125
126 attributes.add(SCIMAttribute.create(
127 attributeDescriptor.getSubAttribute("name"),
128 SCIMAttributeValue.createStringValue(value.getName())));
129
130 attributes.add(SCIMAttribute.create(
131 attributeDescriptor.getSubAttribute("type"),
132 SCIMAttributeValue.createStringValue(
133 value.getDataType().toString())));
134
135 attributes.add(SCIMAttribute.create(
136 attributeDescriptor.getSubAttribute("description"),
137 SCIMAttributeValue.createStringValue(value.getDescription())));
138
139 attributes.add(SCIMAttribute.create(
140 attributeDescriptor.getSubAttribute("readOnly"),
141 SCIMAttributeValue.createBooleanValue(value.isReadOnly())));
142
143 attributes.add(SCIMAttribute.create(
144 attributeDescriptor.getSubAttribute("required"),
145 SCIMAttributeValue.createBooleanValue(value.isRequired())));
146
147 attributes.add(SCIMAttribute.create(
148 attributeDescriptor.getSubAttribute("caseExact"),
149 SCIMAttributeValue.createBooleanValue(value.isCaseExact())));
150
151 if(value.getCanonicalValues() != null)
152 {
153 final AttributeDescriptor canonicalValuesAttributeDescriptor =
154 attributeDescriptor.getSubAttribute("canonicalValues");
155 final SCIMAttributeValue[] canonicalValues =
156 new SCIMAttributeValue[value.getCanonicalValues().size()];
157 int i = 0;
158 for(Entry<String> canonicalValue : value.getCanonicalValues())
159 {
160 canonicalValues[i++] = Entry.STRINGS_RESOLVER.fromInstance(
161 canonicalValuesAttributeDescriptor, canonicalValue);
162 }
163 attributes.add(SCIMAttribute.create(
164 canonicalValuesAttributeDescriptor, canonicalValues));
165 }
166
167 return SCIMAttributeValue.createComplexValue(attributes);
168 }
169
170 /**
171 * {@inheritDoc}
172 */
173 @Override
174 public AttributeDescriptor toInstance(final SCIMAttributeValue value) {
175 return new AttributeDescriptor(
176 value.getSubAttributeValue("name",
177 AttributeValueResolver.STRING_RESOLVER),
178 DataType.parse(value.getSubAttributeValue(
179 "type",
180 AttributeValueResolver.STRING_RESOLVER)),
181 false, null,
182 value.getSubAttributeValue("description",
183 AttributeValueResolver.STRING_RESOLVER),
184 schema,
185 value.getSubAttributeValue("readOnly",
186 AttributeValueResolver.BOOLEAN_RESOLVER),
187 value.getSubAttributeValue("required",
188 AttributeValueResolver.BOOLEAN_RESOLVER),
189 value.getSubAttributeValue("caseExact",
190 AttributeValueResolver.BOOLEAN_RESOLVER),
191 value.getSubAttributeValues("canonicalValues",
192 Entry.STRINGS_RESOLVER), null);
193 }
194 }
195
196 /**
197 * The <code>AttributeValueResolver</code> that resolves SCIM attribute values
198 * to/from <code>AttributeDescriptor</code> instances.
199 */
200 public static final AttributeValueResolver<AttributeDescriptor>
201 ATTRIBUTE_DESCRIPTOR_RESOLVER = new AttributeDescriptorResolver(false);
202
203 static final class AttributeDescriptorResolver extends
204 AttributeValueResolver<AttributeDescriptor>
205 {
206 private final boolean allowNesting;
207
208 /**
209 * Create a new AttributeDescriptorResolver.
210 *
211 * @param allowNesting <code>true</code> to allow nesting of complex
212 * types or <code>false</code> otherwise.
213 */
214 AttributeDescriptorResolver(final boolean allowNesting) {
215 this.allowNesting = allowNesting;
216 }
217
218 /**
219 * {@inheritDoc}
220 */
221 @Override
222 public AttributeDescriptor toInstance(final SCIMAttributeValue value) {
223 String schemaValue = value.getSubAttributeValue("schema",
224 AttributeValueResolver.STRING_RESOLVER);
225 boolean multiValued = value.getSubAttributeValue("multiValued",
226 AttributeValueResolver.BOOLEAN_RESOLVER);
227 String multiValuedChildName = null;
228 if(multiValued)
229 {
230 multiValuedChildName = value.getSubAttributeValue(
231 "multiValuedAttributeChildName",
232 AttributeValueResolver.STRING_RESOLVER);
233 }
234 return new AttributeDescriptor(
235 value.getSubAttributeValue("name",
236 AttributeValueResolver.STRING_RESOLVER),
237 DataType.parse(value.getSubAttributeValue(
238 "type",
239 AttributeValueResolver.STRING_RESOLVER)),
240 multiValued, multiValuedChildName,
241 value.getSubAttributeValue("description",
242 AttributeValueResolver.STRING_RESOLVER),
243 schemaValue,
244 value.getSubAttributeValue("readOnly",
245 AttributeValueResolver.BOOLEAN_RESOLVER),
246 value.getSubAttributeValue("required",
247 AttributeValueResolver.BOOLEAN_RESOLVER),
248 value.getSubAttributeValue("caseExact",
249 AttributeValueResolver.BOOLEAN_RESOLVER), null,
250 value.getSubAttributeValues(
251 allowNesting ? "attributes" : "subAttributes",
252 allowNesting ? this :
253 new SubAttributeDescriptorResolver(schemaValue)));
254 }
255
256 /**
257 * {@inheritDoc}
258 */
259 @Override
260 public SCIMAttributeValue fromInstance(
261 final AttributeDescriptor attributeDescriptor,
262 final AttributeDescriptor value) throws InvalidResourceException {
263
264 final List<SCIMAttribute> attributes =
265 new ArrayList<SCIMAttribute>(10);
266
267 attributes.add(SCIMAttribute.create(
268 attributeDescriptor.getSubAttribute("name"),
269 SCIMAttributeValue.createStringValue(value.getName())));
270
271 attributes.add(SCIMAttribute.create(
272 attributeDescriptor.getSubAttribute("type"),
273 SCIMAttributeValue.createStringValue(
274 value.getDataType().toString())));
275
276 attributes.add(SCIMAttribute.create(
277 attributeDescriptor.getSubAttribute("multiValued"),
278 SCIMAttributeValue.createBooleanValue(value.isMultiValued())));
279
280 if(value.isMultiValued())
281 {
282 attributes.add(SCIMAttribute.create(
283 attributeDescriptor.getSubAttribute("multiValuedAttributeChildName"),
284 SCIMAttributeValue.createStringValue(
285 value.getMultiValuedChildName())));
286 }
287
288 attributes.add(SCIMAttribute.create(
289 attributeDescriptor.getSubAttribute("description"),
290 SCIMAttributeValue.createStringValue(value.getDescription())));
291
292 attributes.add(SCIMAttribute.create(
293 attributeDescriptor.getSubAttribute("schema"),
294 SCIMAttributeValue.createStringValue(value.getSchema())));
295
296 attributes.add(SCIMAttribute.create(
297 attributeDescriptor.getSubAttribute("readOnly"),
298 SCIMAttributeValue.createBooleanValue(value.isReadOnly())));
299
300 attributes.add(SCIMAttribute.create(
301 attributeDescriptor.getSubAttribute("required"),
302 SCIMAttributeValue.createBooleanValue(value.isRequired())));
303
304 attributes.add(SCIMAttribute.create(
305 attributeDescriptor.getSubAttribute("caseExact"),
306 SCIMAttributeValue.createBooleanValue(value.isCaseExact())));
307
308 if(value.getSubAttributes() != null)
309 {
310 final AttributeDescriptor subAttributesDescriptor =
311 allowNesting ? attributeDescriptor :
312 attributeDescriptor.getSubAttribute("subAttributes");
313 final SCIMAttributeValue[] subAttributeValues =
314 new SCIMAttributeValue[value.getSubAttributes().size()];
315 int i = 0;
316 for(AttributeDescriptor subAttribute : value.getSubAttributes())
317 {
318 subAttributeValues[i++] = (allowNesting ? this :
319 new SubAttributeDescriptorResolver(value.getSchema())).
320 fromInstance(subAttributesDescriptor, subAttribute);
321 }
322 attributes.add(SCIMAttribute.create(
323 subAttributesDescriptor, subAttributeValues));
324 }
325
326 return SCIMAttributeValue.createComplexValue(attributes);
327 }
328 }
329
330 private final String schema;
331
332 private final String name;
333
334 private final String description;
335
336 private final boolean readOnly;
337
338 private final boolean required;
339
340 private final boolean multiValued;
341
342 private final String multiValuedChildName;
343
344 private final boolean caseExact;
345
346 private final DataType dataType;
347
348 private final Map<String, AttributeDescriptor> subAttributes;
349
350 private final Collection<Entry<String>> canonicalValues;
351
352 /**
353 * Construct a new AttributeDescriptor instance with the provided info.
354 *
355 * @param name The attribute's name.
356 * @param dataType The attribute's data type.
357 * @param multiValued Whether the attribute is multiValued.
358 * @param multiValuedChildName String value specifying the child XML element
359 * name.
360 * @param description The attribute's human readable description.
361 * @param schema The attribute's associated schema.
362 * @param readOnly Whether the attribute is mutable.
363 * @param required Whether the attribute is required.
364 * @param caseExact Whether the string attribute is case sensitive.
365 * @param canonicalValues A list of canonical type values.
366 * @param subAttributes A list specifying the contained attributes.
367 */
368 private AttributeDescriptor(final String name, final DataType dataType,
369 final boolean multiValued,
370 final String multiValuedChildName,
371 final String description, final String schema,
372 final boolean readOnly, final boolean required,
373 final boolean caseExact,
374 final Collection<Entry<String>> canonicalValues,
375 final Collection<AttributeDescriptor> subAttributes)
376 {
377 this.name = name;
378 this.dataType = dataType;
379 this.multiValued = multiValued;
380 this.multiValuedChildName = multiValuedChildName;
381 this.description = description;
382 this.schema = schema;
383 this.readOnly = readOnly;
384 this.required = required;
385 this.caseExact = caseExact;
386
387 if(canonicalValues != null && !canonicalValues.isEmpty())
388 {
389 this.canonicalValues = canonicalValues;
390 }
391 else
392 {
393 this.canonicalValues = null;
394 }
395
396 if(subAttributes != null && !subAttributes.isEmpty())
397 {
398 this.subAttributes =
399 new LinkedHashMap<String, AttributeDescriptor>(subAttributes.size());
400 for(AttributeDescriptor attributeDescriptor : subAttributes)
401 {
402 this.subAttributes.put(toLowerCase(
403 attributeDescriptor.getName()),
404 attributeDescriptor);
405 }
406 }
407 else
408 {
409 this.subAttributes = null;
410 }
411 }
412
413
414 /**
415 * The URI for the schema that defines the SCIM attribute.
416 *
417 * @return The URI for the schema that defines the SCIM attribute.
418 * It is never {@code null}.
419 */
420 public String getSchema() {
421 return schema;
422 }
423
424
425 /**
426 * The attribute name to be used in any external representation of the SCIM
427 * attribute.
428 *
429 * @return The attribute name to be used in any external representation of
430 * the SCIM attribute. It is never {@code null}.
431 */
432 public String getName() {
433 return name;
434 }
435
436 /**
437 * Indicates whether the attribute is a multi-valued attribute.
438 *
439 * @return {@code true} if the attribute is multi-valued.
440 */
441 public boolean isMultiValued() {
442 return multiValued;
443 }
444
445
446 /**
447 * The child XML element name for multi-valued attributes; e.g., the
448 * 'emails' attribute value is 'email', 'phoneNumbers', is 'phoneNumber'.
449 *
450 * @return The child XML element name or {@code null} if this attribute
451 * is not multi-valued.
452 */
453 public String getMultiValuedChildName() {
454 return multiValuedChildName;
455 }
456
457 /**
458 * Retrieves the set of descriptors for subordinate attributes of a complex
459 * attribute.
460 *
461 * @return The set of descriptors for subordinate attributes of a complex
462 * attribute, or {@code null} if the attribute is not a complex
463 * attribute.
464 */
465 public Collection<AttributeDescriptor> getSubAttributes()
466 {
467 return subAttributes == null ? null : subAttributes.values();
468 }
469
470
471 /**
472 * Retrieves the attribute descriptor for a specified subordinate attribute
473 * of a complex attribute.
474 *
475 * @param externalName The external name of the subordinate attribute for
476 * which a descriptor is required.
477 * @return The attribute descriptor for the specified subordinate attribute.
478 * @throws InvalidResourceException if there is no such attribute.
479 */
480 public AttributeDescriptor getSubAttribute(final String externalName)
481 throws InvalidResourceException
482 {
483 // TODO: Should we have a strict and non strict mode?
484 AttributeDescriptor subAttribute =
485 subAttributes == null ? null :
486 subAttributes.get(toLowerCase(externalName));
487 if(subAttribute == null)
488 {
489 throw new InvalidResourceException("Sub-attribute " + externalName +
490 " is not defined for attribute " + schema +
491 SCIMConstants.SEPARATOR_CHAR_QUALIFIED_ATTRIBUTE + name);
492 }
493 return subAttribute;
494 }
495
496
497 /**
498 * Retrieves a list of canonical type values.
499 *
500 * @return A list of canonical type values or <code>null</code> if the
501 * attribute is not a multi-valued attribute or if they are not
502 * specified.
503 */
504 public Collection<Entry<String>> getCanonicalValues()
505 {
506 return canonicalValues;
507 }
508
509
510 /**
511 * Retrieve the data type for this attribute.
512 *
513 * @return The data type for this attribute, or {@code null} if the attribute
514 * is not a simple attribute.
515 */
516 public DataType getDataType()
517 {
518 return dataType;
519 }
520
521
522
523 /**
524 * Retrieves this attribute's human readable description.
525 *
526 * @return This attribute's human redable description.
527 */
528 public String getDescription()
529 {
530 return description;
531 }
532
533 /**
534 * Specifies if this attribute is mutable.
535 *
536 * @return <code>false</code> if this attribute is mutable or
537 * <code>true</code> otherwise.
538 */
539 public boolean isReadOnly()
540 {
541 return readOnly;
542 }
543
544 /**
545 * Specifies if this attribute is required.
546 *
547 * @return <code>true</code> if this attribute is required for
548 * <code>false</code> otherwise.
549 */
550 public boolean isRequired()
551 {
552 return required;
553 }
554
555 /**
556 * Specifies if the string attribute is case sensitive.
557 *
558 * @return <code>true</code> if this attribute is case sensitive or
559 * <code>false</code> otherwise.
560 */
561 public boolean isCaseExact()
562 {
563 return caseExact;
564 }
565
566 @Override
567 public String toString()
568 {
569 return "AttributeDescriptor{" +
570 "schema='" + getSchema() + '\'' +
571 ", name='" + getName() + '\'' +
572 ", description='" + getDescription() + '\'' +
573 ", multiValued=" + isMultiValued() +
574 ", dataType=" + getDataType() +
575 ", isRequired=" + isRequired() +
576 ", isReadOnly=" + isReadOnly() +
577 ", isCaseExact=" + isCaseExact() +
578 '}';
579 }
580
581 /**
582 * {@inheritDoc}
583 */
584 @Override
585 public int hashCode()
586 {
587 int hashCode = 0;
588
589 hashCode += toLowerCase(schema).hashCode();
590 hashCode += toLowerCase(name).hashCode();
591
592 return hashCode;
593 }
594
595 /**
596 * {@inheritDoc}
597 */
598 @Override
599 public boolean equals(final Object obj)
600 {
601 if (this == obj)
602 {
603 return true;
604 }
605
606 if (!(obj instanceof AttributeDescriptor))
607 {
608 return false;
609 }
610
611 final AttributeDescriptor that = (AttributeDescriptor)obj;
612 if (this.schema == null && that.schema == null)
613 {
614 return this.name.equalsIgnoreCase(that.name);
615 }
616 else
617 {
618 return this.schema != null && that.schema != null &&
619 this.schema.equalsIgnoreCase(that.schema) &&
620 this.name.equalsIgnoreCase(that.name);
621 }
622 }
623
624 /**
625 * Create a new simple attribute descriptor with the provided
626 * information.
627 *
628 * @param name The attribute's name.
629 * @param dataType The attribute's data type.
630 * @param description The attribute's human readable description.
631 * @param schema The attribute's associated schema.
632 * @param readOnly Whether the attribute is mutable.
633 * @param required Whether the attribute is required.
634 * @param caseExact Whether the string attribute is case sensitive.
635 * @param canonicalValues A collection of canonical values.
636 * @return A new singular simple attribute descriptor with the
637 * provided information.
638 */
639 public static AttributeDescriptor createSubAttribute(
640 final String name, final DataType dataType, final String description,
641 final String schema, final boolean readOnly, final boolean required,
642 final boolean caseExact, final String... canonicalValues)
643 {
644 final Collection<Entry<String>> values;
645 if(canonicalValues != null && canonicalValues.length > 0)
646 {
647 values = new ArrayList<Entry<String>>(canonicalValues.length);
648 for(String canonicalValue : canonicalValues)
649 {
650 values.add(new Entry<String>(canonicalValue, null, false));
651 }
652 }
653 else
654 {
655 values = null;
656 }
657 return new AttributeDescriptor(name, dataType, false, null,
658 description, schema, readOnly, required, caseExact, values, null);
659 }
660
661
662
663 /**
664 * Create a new complex attribute descriptor with the provided information.
665 *
666 *
667 * @param name The attribute's name.
668 * @param dataType The attribute's data type.
669 * attribute is multi-valued or {@code null}.
670 * @param description The attribute's human readable description.
671 * @param schema The attribute's associated schema.
672 * @param readOnly Whether the attribute is mutable.
673 * @param required Whether the attribute is required.
674 * @param caseExact Whether the string attribute is case sensitive.
675 * @param subAttributes A list specifying the contained attributes.
676 * @return A new singular complex attribute descriptor
677 * with the provided information.
678 */
679 public static AttributeDescriptor createAttribute(
680 final String name, final DataType dataType, final String description,
681 final String schema, final boolean readOnly, final boolean required,
682 final boolean caseExact, final AttributeDescriptor... subAttributes)
683 {
684 return newAttribute(name, null, dataType, description, schema, readOnly,
685 required, caseExact, subAttributes);
686 }
687
688
689
690 /**
691 * Create a new multi-valued attribute descriptor with the provided
692 * information. The normative sub-attributes for multi-valued attributes
693 * (ie. type, primary, display, operation, value) will be added.
694 *
695 *
696 * @param name The attribute's name.
697 * @param multiValuedChildName The attribute's child XML element name if this.
698 * @param dataType The attribute's data type.
699 * attribute is multi-valued or {@code null}.
700 * @param description The attribute's human readable description.
701 * @param schema The attribute's associated schema.
702 * @param readOnly Whether the attribute is mutable.
703 * @param required Whether the attribute is required.
704 * @param caseExact Whether the string attribute is case sensitive.
705 * @param canonicalValues The list of canonical values for the type attribute.
706 * @param subAttributes A list specifying the contained attributes.
707 * @return A new singular complex attribute descriptor
708 * with the provided information.
709 */
710 public static AttributeDescriptor createMultiValuedAttribute(
711 final String name, final String multiValuedChildName,
712 final DataType dataType, final String description, final String schema,
713 final boolean readOnly, final boolean required, final boolean caseExact,
714 final String[] canonicalValues,
715 final AttributeDescriptor... subAttributes)
716 {
717 return newAttribute(name, multiValuedChildName, dataType,
718 description, schema, readOnly, required, caseExact,
719 CoreSchema.addCommonMultiValuedSubAttributes(
720 dataType, canonicalValues, subAttributes));
721 }
722
723
724
725 /**
726 * Create a new attribute descriptor with the provided information.
727 *
728 *
729 * @param name The attribute's name.
730 * @param multiValuedChildName The attribute's child XML element name if this.
731 * @param dataType The attribute's data type.
732 * attribute is multi-valued or {@code null}.
733 * @param description The attribute's human readable description.
734 * @param schema The attribute's associated schema.
735 * @param readOnly Whether the attribute is mutable.
736 * @param required Whether the attribute is required.
737 * @param caseExact Whether the string attribute is case sensitive.
738 * @param subAttributes A list specifying the contained attributes.
739 * @return A new singular complex attribute descriptor
740 * with the provided information.
741 */
742 static AttributeDescriptor newAttribute(
743 final String name, final String multiValuedChildName,
744 final DataType dataType, final String description, final String schema,
745 final boolean readOnly, final boolean required, final boolean caseExact,
746 final AttributeDescriptor... subAttributes)
747 {
748 return new AttributeDescriptor(name, dataType, multiValuedChildName != null,
749 multiValuedChildName, description, schema, readOnly, required,
750 caseExact, null, Arrays.asList(subAttributes));
751 }
752 }