/* * ==================== * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. * * The contents of this file are subject to the terms of the Common Development * and Distribution License("CDDL") (the "License"). You may not use this file * except in compliance with the License. * * You can obtain a copy of the License at * http://opensource.org/licenses/cddl1.php * See the License for the specific language governing permissions and limitations * under the License. * * When distributing the Covered Code, include this CDDL Header Notice in each file * and include the License file at http://opensource.org/licenses/cddl1.php. * If applicable, add the following below this CDDL Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * ==================== */ package org.identityconnectors.framework.common.objects; import static org.identityconnectors.framework.common.objects.NameUtil.nameHashCode; import static org.identityconnectors.framework.common.objects.NameUtil.namesEqual; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.identityconnectors.common.CollectionUtil; import org.identityconnectors.common.StringUtil; import org.identityconnectors.common.security.GuardedString; /** * Represents a named collection of values within a target object, although the * simplest case is a name-value pair (e.g., email, employeeID). Values can be * empty, null, or set with various types. Empty and null are supported because * it makes a difference on some resources (in particular database resources). * <p> * The developer of a Connector should use an {@link AttributeBuilder} to * construct an instance of Attribute. * <p> * The precise meaning of an instance of {@code Attribute} depends on the * context in which it occurs. * <ul> * <li>When * {@linkplain org.identityconnectors.framework.api.operations.GetApiOp#getObject * an object is read} or is returned by * {@linkplain org.identityconnectors.framework.api.operations.SearchApiOp#search * search}, an {@code Attribute} represents the <i>complete state</i> of an * attribute of the target object, current as of the point in time that the * object was read.</li> * <li>When an {@code Attribute} is supplied to * {@linkplain org.identityconnectors.framework.api.operations.UpdateApiOp the * update operation}, the {@code Attribute} represents a change to the * corresponding attribute of the target object: * <ul> * <li>For calls to * {@link org.identityconnectors.framework.api.operations.UpdateApiOp#update(ObjectClass, Uid, java.util.Set, OperationOptions) * update}, the {@code Attribute} contains the <i>complete, intended state</i> * of the attribute.</li> * <li>When the update type is * {@link org.identityconnectors.framework.api.operations.UpdateApiOp#addAttributeValues(ObjectClass, Uid, java.util.Set, OperationOptions) * addAttributeValues}, the {@code Attribute} contains <i>values to append</i>.</li> * <li>When the update type is * {@link org.identityconnectors.framework.api.operations.UpdateApiOp#removeAttributeValues(ObjectClass, Uid, java.util.Set, OperationOptions) * removeAttributeValues}, the {@code Attribute} contains <i>values to * remove</i>.</li> * </ul> * </li> * <li>When an {@code Attribute} is used to build a * {@link org.identityconnectors.framework.common.objects.filter.Filter Filter} * that is an argument to * {@linkplain org.identityconnectors.framework.api.operations.SearchApiOp#search * search}, an {@code Attribute} represents a <i>subset of the current state</i> * of an attribute that will be used as a search criterion. Specifically, the * {@code Attribute} {@linkplain #getName() names the attribute to match} and * {@linkplain #getValue() contains the values to match}.</li> * </ul> * * TODO: define the set of allowed values * * @author Will Droste * @since 1.0 */ public class Attribute { /** * Name of the {@link Attribute}. */ private final String name; /** * Values of the {@link Attribute}. */ private final List<Object> value; /** * Create an attribute. */ Attribute(String name, List<Object> value) { if (StringUtil.isBlank(name)) { throw new IllegalArgumentException("Name must not be blank!"); } if (OperationalAttributes.PASSWORD_NAME.equals(name) || OperationalAttributes.CURRENT_PASSWORD_NAME.equals(name)) { // check the value.. if (value == null || value.size() != 1) { throw new IllegalArgumentException("Must be a single value."); } if (!(value.get(0) instanceof GuardedString)) { throw new IllegalArgumentException( "Password value must be an instance of GuardedString"); } } // make this case insensitive this.name = name; // copy to prevent corruption.. this.value = (value == null) ? null : CollectionUtil.newReadOnlyList(value); } public String getName() { return this.name; } public List<Object> getValue() { return (this.value == null) ? null : Collections.unmodifiableList(this.value); } /** * Determines if the 'name' matches this {@link Attribute}. * * @param name * case insensitive string representation of the attribute's * name. * @return <code>true</code> if the case insentitive name is equal to that * of the one in {@link Attribute}. */ public boolean is(String name) { return namesEqual(this.name, name); } // =================================================================== // Object Overrides // =================================================================== @Override public final int hashCode() { return nameHashCode(name); } @Override public String toString() { // poor man's consistent toString impl.. StringBuilder bld = new StringBuilder(); bld.append("Attribute: "); Map<String, Object> map = new LinkedHashMap<String, Object>(); map.put("Name", getName()); map.put("Value", getValue()); bld.append(map); return bld.toString(); } @Override public final boolean equals(Object obj) { // test identity if (this == obj) { return true; } // test for null.. if (obj == null) { return false; } // test that the exact class matches if (!(getClass().equals(obj.getClass()))) { return false; } // test name field.. final Attribute other = (Attribute) obj; if (!is(other.name)) { return false; } if (!CollectionUtil.equals(value, other.value)) { return false; } return true; } }