001/*
002 * Copyright 2011-2016 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
018package com.unboundid.scim.data;
019
020import com.unboundid.scim.marshal.Marshaller;
021import com.unboundid.scim.schema.AttributeDescriptor;
022import com.unboundid.scim.schema.ResourceDescriptor;
023import com.unboundid.scim.sdk.ComplexValue;
024import com.unboundid.scim.sdk.InvalidResourceException;
025import com.unboundid.scim.sdk.SCIMAttribute;
026import com.unboundid.scim.sdk.SCIMAttributeValue;
027import com.unboundid.scim.sdk.SCIMConstants;
028import com.unboundid.scim.sdk.SCIMObject;
029import com.unboundid.scim.sdk.SCIMResponse;
030import com.unboundid.scim.sdk.SimpleValue;
031
032import java.io.OutputStream;
033import java.util.ArrayList;
034import java.util.Collection;
035import java.util.Collections;
036import java.util.Iterator;
037
038/**
039 * This class represents a SCIM resource. It could also be sub-typed for
040 * specific resource types (ie. Users or Groups) that provide convenience
041 * methods for accessing specific attribute values.
042 */
043public class BaseResource implements SCIMResponse
044{
045  /**
046   * A <code>ResourceFactory</code> for creating <code>BaseResource</code>
047   * instances.
048   */
049  public static final ResourceFactory<BaseResource> BASE_RESOURCE_FACTORY =
050      new ResourceFactory<BaseResource>() {
051        /**
052         * {@inheritDoc}
053         */
054        public BaseResource createResource(
055            final ResourceDescriptor resourceDescriptor,
056            final SCIMObject scimObject) {
057          return new BaseResource(resourceDescriptor, scimObject);
058        }
059      };
060
061  private final ResourceDescriptor resourceDescriptor;
062  private final SCIMObject scimObject;
063
064  /**
065   * Construct a <code>BaseResource</code> with the specified
066   * <code>ResourceDescriptor</code> and backed by the given
067   * <code>SCIMObject</code>.
068   *
069   * @param resourceDescriptor The resource descriptor for this SCIM resource.
070   * @param scimObject         The <code>SCIMObject</code> containing all the
071   *                           SCIM attributes and their values.
072   */
073  public BaseResource(final ResourceDescriptor resourceDescriptor,
074                      final SCIMObject scimObject)
075  {
076    this.resourceDescriptor = resourceDescriptor;
077    this.scimObject = scimObject;
078  }
079
080  /**
081   * Construct an empty <code>BaseResource</code> with the specified
082   * <code>ResourceDescriptor</code>.
083   *
084   * @param resourceDescriptor The resource descriptor for this SCIM resource.
085   */
086  public BaseResource(final ResourceDescriptor resourceDescriptor)
087  {
088    this.resourceDescriptor = resourceDescriptor;
089    this.scimObject = new SCIMObject();
090  }
091
092  /**
093   * Retrieves the <code>ResourceDescriptor</code> for this resource.
094   *
095   * @return The <code>ResourceDescriptor</code> for this resource.
096   */
097  public ResourceDescriptor getResourceDescriptor() {
098    return resourceDescriptor;
099  }
100
101  /**
102   * Retrieves the <code>SCIMObject</code> wrapped by this resource.
103   *
104   * @return The <code>SCIMObject</code> wrapped by this resource.
105   */
106  public SCIMObject getScimObject() {
107    return scimObject;
108  }
109
110  /**
111   * Retrieves the unique identifier for the SCIM Resource as defined by
112   * the Service Provider.
113   *
114   * @return The unique identifier for the SCIM Resource as defined by
115   * the Service Provider.
116   */
117  public String getId()
118  {
119    return getSingularAttributeValue(SCIMConstants.SCHEMA_URI_CORE, "id",
120        AttributeValueResolver.STRING_RESOLVER);
121  }
122
123  /**
124   * Sets the unique identifier for the SCIM Resource.
125   *
126   * @param id The unique identifier for the SCIM Resource.
127   */
128  public void setId(final String id)
129  {
130    try {
131      setSingularAttributeValue(SCIMConstants.SCHEMA_URI_CORE, "id",
132          AttributeValueResolver.STRING_RESOLVER, id);
133    } catch (InvalidResourceException e) {
134      // This should never happen as these are core attributes...
135      throw new RuntimeException(e);
136    }
137  }
138
139  /**
140   * Retrieves the unique identifier for the Resource as defined by the
141   * Service Consumer.
142   *
143   * @return The unique identifier for the Resource as defined by the Service
144   * Consumer.
145   */
146  public String getExternalId()
147  {
148    return getSingularAttributeValue(SCIMConstants.SCHEMA_URI_CORE,
149        "externalId", AttributeValueResolver.STRING_RESOLVER);
150  }
151
152  /**
153   * Sets the unique identifier for the Resource as defined by the Service
154   * Consumer.
155   *
156   * @param externalId The unique identifier for the Resource as defined by the
157   * Service Consumer.
158   */
159  public void setExternalId(final String externalId)
160  {
161    try {
162      setSingularAttributeValue(SCIMConstants.SCHEMA_URI_CORE, "externalId",
163          AttributeValueResolver.STRING_RESOLVER, externalId);
164    } catch (InvalidResourceException e) {
165      // This should never happen as these are core attributes...
166      throw new RuntimeException(e);
167    }
168  }
169
170
171  /**
172   * Retrieves the metadata about the resource.
173   *
174   * @return The metadata about the resource.
175   */
176  public Meta getMeta()
177  {
178    return getSingularAttributeValue(SCIMConstants.SCHEMA_URI_CORE, "meta",
179        Meta.META_RESOLVER);
180  }
181
182  /**
183   * Sets the metadata about the resource.
184   * @param meta The metadata about the resource.
185   */
186  public void setMeta(final Meta meta)
187  {
188    try {
189      setSingularAttributeValue(SCIMConstants.SCHEMA_URI_CORE, "meta",
190          Meta.META_RESOLVER, meta);
191    } catch (InvalidResourceException e) {
192      // This should never happen as these are core attributes...
193      throw new RuntimeException(e);
194    }
195  }
196
197  /**
198   * Retrieves a singular attribute value.
199   *
200   * @param <T>    The type of the resolved instance representing the value of
201   *               sub-attribute.
202   * @param schema The schema URI of the attribute value to retrieve.
203   * @param name The name of the attribute value to retrieve.
204   * @param resolver The <code>AttributeValueResolver</code> the should be used
205   *                 to resolve the value to an instance.
206   * @return The resolved value instance or <code>null</code> if the specified
207   *         attribute does not exist.
208   */
209  public <T> T getSingularAttributeValue(
210      final String schema, final String name,
211      final AttributeValueResolver<T> resolver)
212  {
213    SCIMAttribute attribute = scimObject.getAttribute(schema, name);
214    if(attribute != null)
215    {
216      SCIMAttributeValue value = attribute.getValue();
217      if(value != null)
218      {
219        return resolver.toInstance(value);
220      }
221    }
222    return null;
223  }
224
225
226  /**
227   * Sets a singular attribute value.
228   *
229   * @param <T>    The type of the resolved instance representing the value of
230   *               sub-attribute.
231   * @param schema The schema URI of the attribute value to retrieve.
232   * @param name The name of the attribute value to retrieve.
233   * @param resolver The <code>AttributeValueResolver</code> the should be used
234   *                 to resolve the instance to attribute value.
235   * @param value The value instance.
236   * @throws InvalidResourceException if the attribute is not defined by the
237   *                                  resource.
238   */
239  public <T> void setSingularAttributeValue(
240      final String schema, final String name,
241      final AttributeValueResolver<T> resolver, final T value)
242      throws InvalidResourceException
243  {
244    if(value == null)
245    {
246      scimObject.removeAttribute(schema, name);
247      return;
248    }
249
250    AttributeDescriptor attributeDescriptor =
251        getResourceDescriptor().getAttribute(schema, name);
252
253    scimObject.setAttribute(SCIMAttribute.create(
254        attributeDescriptor, resolver.fromInstance(attributeDescriptor,
255        value)));
256  }
257
258  /**
259   * Retrieves a multi-valued attribute value.
260   *
261   * @param <T>    The type of the resolved instance representing the value of
262   *               sub-attribute.
263   * @param schema The schema URI of the attribute value to retrieve.
264   * @param name The name of the attribute value to retrieve.
265   * @param resolver The <code>AttributeValueResolver</code> the should be used
266   *                 to resolve the value to an instance.
267   * @return The collection of resolved value instances or <code>null</code> if
268   *         the specified attribute does not exist.
269   */
270  public <T> Collection<T> getAttributeValues(
271      final String schema, final String name,
272      final AttributeValueResolver<T> resolver)
273  {
274    SCIMAttribute attribute = scimObject.getAttribute(schema, name);
275    if(attribute != null)
276    {
277      SCIMAttributeValue[] values = attribute.getValues();
278      if(values != null)
279      {
280        Collection<T> entries = new ArrayList<T>(values.length);
281        for(SCIMAttributeValue v : values)
282        {
283          entries.add(resolver.toInstance(v));
284        }
285        return entries;
286      }
287    }
288    return null;
289  }
290
291
292  /**
293   * Sets a multi-valued attribute value.
294   *
295   * @param <T>    The type of the resolved instance representing the value of
296   *               sub-attribute.
297   * @param schema The schema URI of the attribute value to retrieve.
298   * @param name The name of the attribute value to retrieve.
299   * @param resolver The <code>AttributeValueResolver</code> the should be used
300   *                 to resolve the instance to attribute value.
301   * @param values The value instances.
302   * @throws InvalidResourceException if the attribute is not defined by the
303   *                                  resource.
304   */
305  public <T> void setAttributeValues(
306      final String schema, final String name,
307      final AttributeValueResolver<T> resolver, final Collection<T> values)
308      throws InvalidResourceException
309  {
310    if(values == null)
311    {
312      scimObject.removeAttribute(schema, name);
313      return;
314    }
315
316    AttributeDescriptor attributeDescriptor =
317        getResourceDescriptor().getAttribute(schema, name);
318
319    SCIMAttributeValue[] entries = new SCIMAttributeValue[values.size()];
320
321    int i = 0;
322    for(T value : values)
323    {
324      entries[i++] = resolver.fromInstance(attributeDescriptor, value);
325    }
326
327    scimObject.setAttribute(
328        SCIMAttribute.create(attributeDescriptor, entries));
329  }
330
331
332
333  /**
334   * Set a simple attribute value with the specified name, using an
335   * <code>AttributeValueResolver</code> appropriate for the attribute
336   * data type.
337   *
338   * @param name the name of the attribute to be set, must be unique among all
339   *             schemas in use by the Resource.
340   * @param value the new attribute value.
341   * @throws InvalidResourceException if no attribute with the specified name
342   * is defined on the Resource, if the name is ambiguous, or if the
343   * attribute is not of a simple data type.
344   */
345  public void setSimpleAttribute(
346      final String name,
347      final SimpleValue value) throws InvalidResourceException {
348
349    setSimpleAttribute(findDescriptor(name), value);
350
351  }
352
353
354  /**
355   * Set a simple attribute value with the specified schema and name,
356   * using an <code>AttributeValueResolver</code> appropriate for
357   * the attribute data type.
358   *
359   * @param schema Schema URI of the attribute to be set.
360   * @param name the name of the attribute to be set.
361   * @param value the new attribute value.
362   * @throws InvalidResourceException if no attribute with the specified name
363   * is defined by the schema, or if the attribute is not of a simple data
364   * type.
365   */
366  public void setSimpleAttribute(
367      final String schema,
368      final String name,
369      final SimpleValue value) throws InvalidResourceException {
370
371    setSimpleAttribute(
372        getResourceDescriptor().getAttribute(schema, name), value);
373
374  }
375
376
377  /**
378   * Set a complex attribute value with the specified name.
379   *
380   * @param name the name of the attribute to be set, must be unique among all
381   *             schemas in use by the Resource.
382   * @param attributeValue ComplexValue object containing a map of
383   *                       sub-attribute names and values
384   * @throws InvalidResourceException if no attribute with the specified name
385   * is defined on the Resource, if the name is ambiguous, or if the
386   * attribute is not of a complex data type.
387   */
388  public void setComplexAttribute(
389      final String name,
390      final ComplexValue attributeValue)
391      throws InvalidResourceException {
392
393    setComplexAttribute(findDescriptor(name), attributeValue);
394  }
395
396
397  /**
398   * Set a complex attribute value with the specified schema and name.
399   *
400   * @param schema Schema URI of the attribute to be set.
401   * @param name The name of the attribute to be set.
402   * @param attributeValue ComplexValue object containing a map of
403   *                       sub-attribute names and values.
404   * @throws InvalidResourceException if the name is not defined by the
405   * specified schema or the attribute is not of a complex data type.
406   */
407  public void setComplexAttribute(
408      final String schema,
409      final String name,
410      final ComplexValue attributeValue)
411      throws InvalidResourceException {
412
413    setComplexAttribute(
414        getResourceDescriptor().getAttribute(schema, name),
415        attributeValue);
416  }
417
418
419  /**
420   * Set all values of a multi-valued attribute with the specified name.
421   * If the attribute already exists, all values are overwritten.
422   *
423   * @param name The name of the attribute to be set, must be unique among all
424   *             schemas in use by the Resource.
425   * @param values A collection of complex attribute values.
426   * @throws InvalidResourceException if no attribute with the specified name
427   * is defined on the Resource, if the name is  ambiguous, or if the
428   * attribute is not multi-valued.
429   */
430  public void setMultiValuedAttribute(
431      final String name,
432      final Collection<ComplexValue> values)
433      throws InvalidResourceException {
434
435    setMultiValuedAttribute(findDescriptor(name), values);
436  }
437
438
439
440  /**
441   * Set all values of a multi-valued attribute with the specified schema and
442   * name.  If the attribute already exists, all values are overwritten.
443   *
444   * @param schema Schema URI of the attribute to be set.
445   * @param name The name of the attribute to be set.
446   * @param values A collection of complex attribute values.
447   * @throws InvalidResourceException if the name is not defined by the
448   * specified schema or if the attribute is not multi-valued.
449   */
450  public void setMultiValuedAttribute(
451      final String schema,
452      final String name,
453      final Collection<ComplexValue> values)
454      throws InvalidResourceException {
455
456    setMultiValuedAttribute(
457        getResourceDescriptor().getAttribute(schema, name),
458        values);
459  }
460
461
462  /**
463   * Add or replace a value on a multi-valued attribute with the specified
464   * name. If the attribute includes a canonical type sub-attribute
465   * then a value matching the type will be overwritten.  Otherwise the new
466   * value is added to the value list of the attribute.
467   *
468   * @param name The name of the attribute to be set, must be unique among all
469   *             schemas in use by the Resource.
470   * @param attributeValue the complex attribute value.
471   * @throws InvalidResourceException if no attribute with the specified name
472   * is defined on the Resource, if the name is  ambiguous, or if the
473   * attribute is not multi-valued.
474   */
475  public void addOrReplaceMultiValuedValue(
476      final String name,
477      final ComplexValue attributeValue)
478      throws InvalidResourceException {
479
480    addOrReplaceMultiValuedValue(findDescriptor(name), attributeValue);
481  }
482
483
484  /**
485   * Add or replace a value on a multi-valued attribute with the specified
486   * schema and name. If the attribute includes a canonical type sub-attribute
487   * then a value matching the type will be overwritten.  Otherwise the new
488   * value is added to the value list of the attribute.
489   *
490   * @param schema Schema URI of the attribute to be set.
491   * @param name  The name of the attribute to be set.
492   * @param attributeValue the complex attribute value .
493   * @throws InvalidResourceException if the attribute name is not
494   * defined by the specified schema or if the attribute is not
495   * multi-valued.
496   */
497  public void addOrReplaceMultiValuedValue(
498      final String schema,
499      final String name,
500      final ComplexValue attributeValue)
501      throws InvalidResourceException {
502
503    addOrReplaceMultiValuedValue(
504        getResourceDescriptor().getAttribute(schema, name),
505        attributeValue);
506  }
507
508
509
510  /**
511   * Get a simple attribute with the specified name.
512   *
513   * @param name The name of the attribute to retrieve, must be unique among
514   *             all schemas in use by the Resource.
515   * @return A SimpleValue, or null if the attribute is not present
516   * on this Resource instance.
517   * @throws InvalidResourceException if no attribute with the specified name
518   * is defined on the Resource, or if the name is ambiguous.
519   */
520  public SimpleValue getSimpleAttributeValue(final String name)
521      throws InvalidResourceException {
522
523    return getSimpleAttributeValue(findDescriptor(name));
524  }
525
526
527  /**
528   * Get a simple attribute with the specified name and schema.
529   *
530   * @param schema The schema URI of the attribute to retrieve.
531   * @param name The name of the attribute to retrieve.
532   * @return A SimpleValue, or null if the attribute is not present
533   * on this Resource instance.
534   * @throws InvalidResourceException if the attribute is not defined
535   * by the resource schema or is not of a simple type.
536   */
537  public SimpleValue getSimpleAttributeValue(
538      final String schema,
539      final String name)
540      throws InvalidResourceException {
541
542    return getSimpleAttributeValue(getDescriptor(name, schema));
543  }
544
545
546  /**
547   * Get a complex attribute with the specified name.
548   *
549   * @param name The name of the attribute to retrieve, must be unique among
550   *             all schemas in use by the Resource.
551   * @return A ComplexValue, or null if the attribute is not present
552   * on this Resource instance.
553   * @throws InvalidResourceException if no attribute with the specified name
554   * is defined on the Resource, or if the name is ambiguous.
555   */
556  public ComplexValue getComplexAttributeValue(final String name)
557      throws InvalidResourceException {
558
559    return getComplexAttributeValue(findDescriptor(name));
560  }
561
562
563  /**
564   * Get a complex attribute with the specified name and schema.
565   *
566   * @param schema The schema URI of the attribute to retrieve.
567   * @param name The name of the attribute to retrieve.
568   * @return A ComplexValue, or null if the attribute is not present
569   * on this Resource instance.
570   * @throws InvalidResourceException if the attribute is not defined
571   * by the resource schema or is not complex.
572   */
573  public ComplexValue getComplexAttributeValue(
574      final String schema,
575      final String name)
576      throws InvalidResourceException {
577
578    return getComplexAttributeValue(getDescriptor(name, schema));
579  }
580
581
582  /**
583   * Get the values of a multi-valued attribute with the specified name.
584   *
585   * @param name The name of the attribute to retrieve, must be unique among
586   *             all schemas in use by the Resource.
587   * @return A Collection of ComplexValue, one for each value instance,
588   * or null if the attribute is not present on this Resource instance.
589   * @throws InvalidResourceException if the attribute is not defined by
590   * this SCIMResource or if the attribute is not multi-valued.
591   */
592  public Collection<ComplexValue> getMultiValuedAttribute(
593      final String name) throws InvalidResourceException {
594
595    return getAttributeValues(findDescriptor(name));
596  }
597
598
599  /**
600   * Get the values of the multi-valued attribute specified by schema and
601   * attribute name.
602   *
603   * @param schema The schema URI of the attribute to retrieve.
604   * @param name The name of the attribute to retrieve.
605   * @return A Collection of ComplexValue, one for each value instance,
606   * or null if the attribute is not present on this Resource instance.
607   * @throws InvalidResourceException if the attribute is not defined by
608   * this SCIMResource or if the attribute is not multi-valued.
609   */
610  public Collection<ComplexValue> getMultiValuedAttribute(
611      final String schema,
612      final String name) throws InvalidResourceException {
613
614    return getAttributeValues(getDescriptor(name, schema));
615  }
616
617
618  /**
619   * {@inheritDoc}
620   */
621  public void marshal(final Marshaller marshaller,
622                      final OutputStream outputStream)
623      throws Exception {
624    marshaller.marshal(this, outputStream);
625  }
626
627  /**
628   * {@inheritDoc}
629   */
630  @Override
631  public boolean equals(final Object o) {
632    if (this == o) {
633      return true;
634    }
635    if (!(o instanceof BaseResource)) {
636      return false;
637    }
638
639    BaseResource that = (BaseResource) o;
640
641    if (!resourceDescriptor.equals(that.resourceDescriptor)) {
642      return false;
643    }
644    if (!scimObject.equals(that.scimObject)) {
645      return false;
646    }
647
648    return true;
649  }
650
651  /**
652   * {@inheritDoc}
653   */
654  @Override
655  public int hashCode() {
656    int result = resourceDescriptor.hashCode();
657    result = 31 * result + scimObject.hashCode();
658    return result;
659  }
660
661
662
663  /**
664   * {@inheritDoc}
665   */
666  @Override
667  public String toString()
668  {
669    final StringBuilder sb = new StringBuilder();
670    sb.append("BaseResource");
671    sb.append("{resource=").append(resourceDescriptor.getSchema());
672    sb.append(SCIMConstants.SEPARATOR_CHAR_QUALIFIED_ATTRIBUTE);
673    sb.append(resourceDescriptor.getName());
674    sb.append(", scimObject=").append(scimObject);
675    sb.append('}');
676    return sb.toString();
677  }
678
679
680  /**
681   * Find the attribute with the given name but unspecified schema.
682   * @param attributeName the name of the attribute to find
683   * @return AttributeDescriptor for the found attribute
684   * @throws InvalidResourceException if the specified attribute name
685   * is not defined for this Resource, or if it is used by more than one
686   * attribute schema.
687   */
688  private AttributeDescriptor findDescriptor(final String attributeName)
689      throws InvalidResourceException {
690
691    AttributeDescriptor attributeDescriptor = null;
692
693    for (String schema : getResourceDescriptor().getAttributeSchemas()) {
694      AttributeDescriptor d =
695          getResourceDescriptor().findAttribute(schema, attributeName);
696      if (d != null) {
697        if (attributeDescriptor == null) {
698          attributeDescriptor = d;
699        }
700        else {
701          throw new InvalidResourceException(String.format(
702              "Ambiguous attribute: %s is defined by more than one schema.",
703              attributeName));
704        }
705      }
706    }
707    if (attributeDescriptor == null) {
708      throw new InvalidResourceException(
709          String.format("No attribute with name %s defined for this resource",
710              attributeName));
711    }
712    return attributeDescriptor;
713  }
714
715
716  /**
717   * Get the simple attribute defined by the specified AttributeDescriptor.
718   * @param descriptor AttributeDescriptor object
719   * @return A SimpleValue object, or null if the attribute is not
720   * present on this SCIMResource instance.
721   * @throws InvalidResourceException if the attribute is not of a simple
722   * data type.
723   */
724  private SimpleValue getSimpleAttributeValue(
725      final AttributeDescriptor descriptor) throws InvalidResourceException {
726
727    validateNotComplex(descriptor);
728
729    SCIMAttribute attribute = scimObject.getAttribute(descriptor.getSchema(),
730        descriptor.getName());
731    if(attribute != null) {
732      SCIMAttributeValue value = attribute.getValue();
733      if (value != null) {
734        return value.getValue();
735      }
736    }
737    return null;
738  }
739
740
741  /**
742   * Get the complex attribute defined by the specified AttributeDescriptor.
743   * @param descriptor AttributeDescriptor object
744   * @return A Map of sub-attribute values, or null if the attribute is not
745   * present on this SCIMResource instance
746   * @throws InvalidResourceException if the attribute is not defined as
747   * complex.
748   */
749  private ComplexValue getComplexAttributeValue(
750      final AttributeDescriptor descriptor) throws InvalidResourceException {
751
752    validateComplex(descriptor);
753    validateSingular(descriptor);
754
755    return getSingularAttributeValue(
756        descriptor.getSchema(),
757        descriptor.getName(),
758        AttributeValueResolver.COMPLEX_RESOLVER);
759  }
760
761
762  /**
763   * Get all values of a multi-valued attribute.
764   * @param descriptor  AttributeDescriptor object
765   * @return collection of complex attribute values
766   * @throws InvalidResourceException if attribute is not multi-valued
767   */
768  private Collection<ComplexValue> getAttributeValues(
769      final AttributeDescriptor descriptor) throws InvalidResourceException {
770
771    validateMultiValued(descriptor);
772    validateComplex(descriptor);   // is this needed - multi implies complex?
773
774    return getAttributeValues(
775        descriptor.getSchema(),
776        descriptor.getName(),
777        AttributeValueResolver.COMPLEX_RESOLVER);
778
779  }
780
781
782  /**
783   * Set a simple attribute value.
784   * @param descriptor AttributeDescriptor object
785   * @param value the new attribute value
786   * @throws InvalidResourceException if the attribute is not simple.
787   */
788  private void setSimpleAttribute(
789      final AttributeDescriptor descriptor,
790      final SimpleValue value) throws InvalidResourceException {
791
792    validateNotComplex(descriptor);
793
794    scimObject.setAttribute(SCIMAttribute.create(descriptor,
795        SCIMAttributeValue.createSimpleValue(value)));
796  }
797
798
799  /**
800   * Set a complex attribute value.
801   * @param descriptor AttributeDescriptor object
802   * @param attributeValue the ComplexAttributeValue to set
803   * @throws InvalidResourceException if the attribute is not complex.
804   */
805  private void setComplexAttribute(
806      final AttributeDescriptor descriptor,
807      final ComplexValue attributeValue)
808      throws InvalidResourceException {
809
810    validateComplex(descriptor);
811    validateSingular(descriptor);
812
813    setSingularAttributeValue(descriptor.getSchema(),
814        descriptor.getName(),
815        AttributeValueResolver.COMPLEX_RESOLVER,
816        attributeValue);
817  }
818
819
820  /**
821   * Set all values of a multi-valued attribute.  If the attribute already
822   * exists, all values are overwritten.  If the attribute does not exist
823   * it is added.
824   * @param descriptor AttributeDescriptor object
825   * @param values A collection of complex attribute values.
826   * @throws InvalidResourceException if the name is not defined by the
827   * specified schema or if the attribute is not multi-valued.
828   */
829  private void setMultiValuedAttribute(
830      final AttributeDescriptor descriptor,
831      final Collection<ComplexValue> values)
832      throws InvalidResourceException {
833
834    validateMultiValued(descriptor);
835    validateComplex(descriptor);   // is this needed?
836
837    setAttributeValues(descriptor.getSchema(),
838        descriptor.getName(),
839        AttributeValueResolver.COMPLEX_RESOLVER,
840        values);
841  }
842
843
844  /**
845   * Add or replace a value on a multi-valued attribute.
846   * If the attribute includes a canonical type sub-attribute then a value
847   * matching the type will be overwritten.  Otherwise the new value is added
848   * to the value list of the attribute.
849   * @param descriptor AttributeDescriptor object
850   * @param attributeValue the complex attribute vale, as a set of
851   *                           sub-attribute values.
852   * @throws InvalidResourceException if the attribute name is not
853   * defined by the SCIMResource or if the attribute is not
854   * multi-valued.
855   */
856  private void addOrReplaceMultiValuedValue(
857      final AttributeDescriptor descriptor,
858      final ComplexValue attributeValue)
859      throws InvalidResourceException {
860
861    Collection<ComplexValue> currentValues =
862        getAttributeValues(descriptor);
863    if (currentValues == null) {
864      currentValues = Collections.singleton(attributeValue);
865    }
866    else {
867      String canonicalType = attributeValue.getStringValue("type");
868      if (canonicalType != null) {
869        Iterator<ComplexValue> iterator = currentValues.iterator();
870        while (iterator.hasNext()) {
871          ComplexValue value = iterator.next();
872          if (value.containsKey("type") &&
873              value.getStringValue("type").equals(canonicalType)) {
874            // replace this value with the input value
875            iterator.remove();
876            break;
877          }
878        }
879      }
880      currentValues.add(attributeValue);
881    }
882    setAttributeValues(descriptor.getSchema(),
883        descriptor.getName(),
884        AttributeValueResolver.COMPLEX_RESOLVER,
885        currentValues);
886  }
887
888
889
890  /**
891   * Utility method to get the AttributeDescriptor for the specified attribute
892   * name and schema.
893   * @param name attribute name
894   * @param schema attribute schema URI
895   * @return AttributeDescriptor object
896   * @throws InvalidResourceException if the Resource does not define an
897   * attribute with the specified name and schema.
898   */
899  private AttributeDescriptor getDescriptor(
900      final String name,
901      final String schema) throws InvalidResourceException {
902
903    AttributeDescriptor descriptor =
904        getResourceDescriptor().getAttribute(schema, name);
905    if (descriptor == null) {
906      throw new InvalidResourceException(
907          String.format("No attribute with name %s defined with schema %s",
908              name, schema)
909      );
910    }
911    return descriptor;
912  }
913
914
915  /**
916   * Validate that an attribute is a complex attribute.
917   * @param descriptor AttributeDescriptor for the attribute
918   * @throws InvalidResourceException if the attribute is not complex.
919   */
920  private void validateComplex(final AttributeDescriptor descriptor)
921      throws InvalidResourceException {
922    if (!descriptor.getDataType().equals(
923        AttributeDescriptor.DataType.COMPLEX)) {
924      throw new InvalidResourceException(
925          String.format("Attribute %s is not defined as complex.",
926              descriptor.getName()));
927    }
928  }
929
930  /**
931   * Validate that an attribute is not a complex attribute.
932   * @param descriptor AttributeDescriptor for the attribute
933   * @throws InvalidResourceException if the attribute is complex.
934   */
935  private void validateNotComplex(final AttributeDescriptor descriptor)
936      throws InvalidResourceException {
937
938    if (descriptor.getDataType().equals(AttributeDescriptor.DataType.COMPLEX)) {
939      throw new InvalidResourceException(
940          String.format("Attribute %s is defined as complex.",
941              descriptor.getName()));
942    }
943
944  }
945
946
947  /**
948   * Validate that an attribute is multi-valued.
949   * @param descriptor AttributeDescriptor for the attribute
950   * @throws InvalidResourceException if the attribute is not multi-valued.
951   */
952  private void validateMultiValued(final AttributeDescriptor descriptor)
953      throws InvalidResourceException {
954
955    if (!descriptor.isMultiValued()) {
956      throw new InvalidResourceException(String.format(
957          "Attribute %s is not defined as multi-valued.",
958          descriptor.getName()));
959    }
960
961  }
962
963
964  /**
965   * Validate that an attribute is singular.
966   * @param descriptor AttributeDescriptor for the attribute
967   * @throws InvalidResourceException if the attribute is multi-valued.
968   */
969  private void validateSingular(final AttributeDescriptor descriptor)
970      throws InvalidResourceException {
971    if (descriptor.isMultiValued()) {
972      throw new InvalidResourceException(String.format(
973          "Attribute %s is not a singular attribute.",
974          descriptor.getName()));
975    }
976  }
977}