001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2008 Sun Microsystems, Inc.
025 *      Portions Copyright 2015 ForgeRock AS.
026 */
027package org.forgerock.opendj.config;
028
029import static com.forgerock.opendj.util.StaticUtils.*;
030
031import org.forgerock.opendj.ldap.DN;
032import org.forgerock.opendj.ldap.RDN;
033
034/**
035 * A reference to another managed object.
036 *
037 * @param <C>
038 *            The type of client managed object configuration that this
039 *            reference refers to.
040 * @param <S>
041 *            The type of server managed object configuration that this
042 *            reference refers to.
043 */
044public final class Reference<C extends ConfigurationClient, S extends Configuration> {
045
046    /**
047     * Parses a DN string value as a reference using the provided managed object
048     * path and relation definition.
049     *
050     * @param <C>
051     *            The type of client managed object configuration that this
052     *            reference refers to.
053     * @param <S>
054     *            The type of server managed object configuration that this
055     *            reference refers to.
056     * @param path
057     *            The path of the referenced managed object's parent.
058     * @param relationDef
059     *            The instantiable relation in the parent which contains the
060     *            referenced managed object.
061     * @param dnAsString
062     *            The DN string value.
063     * @return Returns the new reference based on the provided DN string value.
064     * @throws IllegalArgumentException
065     *             If the DN string value could not be decoded as a DN or if the
066     *             provided DN did not correspond to the provided path and
067     *             relation.
068     */
069    public static <C extends ConfigurationClient, S extends Configuration> Reference<C, S> parseDN(
070        ManagedObjectPath<?, ?> path, InstantiableRelationDefinition<C, S> relationDef, String dnAsString) {
071        AbstractManagedObjectDefinition<?, ?> definition = path.getManagedObjectDefinition();
072        RelationDefinition<?, ?> tmp = definition.getRelationDefinition(relationDef.getName());
073        if (tmp != relationDef) {
074            // TODO : i18n ?
075            throw new IllegalArgumentException("The relation \"" + relationDef.getName()
076                + "\" is not associated with the definition \"" + definition.getName() + "\"");
077        }
078
079        DN dn = DN.valueOf(dnAsString);
080        RDN rdn = dn.rdn();
081        if (rdn == null) {
082            // TODO : i18n ?
083            throw new IllegalArgumentException("Unabled to decode the DN string: \"" + dnAsString + "\"");
084        }
085
086        // Check that the DN was valid.
087        String name = rdn.getFirstAVA().getAttributeValue().toString();
088        DN expected = path.child(relationDef, name).toDN();
089        if (!dn.equals(expected)) {
090            // TODO : i18n ?
091            throw new IllegalArgumentException("Unabled to decode the DN string: \"" + dnAsString + "\"");
092        }
093
094        return new Reference<>(path, relationDef, name);
095    }
096
097    /**
098     * Parses a name as a reference using the provided managed object path and
099     * relation definition.
100     *
101     * @param <C>
102     *            The type of client managed object configuration that this
103     *            reference refers to.
104     * @param <S>
105     *            The type of server managed object configuration that this
106     *            reference refers to.
107     * @param p
108     *            The path of the referenced managed object's parent.
109     * @param rd
110     *            The instantiable relation in the parent which contains the
111     *            referenced managed object.
112     * @param s
113     *            The name of the referenced managed object.
114     * @return Returns the new reference based on the provided name.
115     * @throws IllegalArgumentException
116     *             If the relation is not associated with the provided parent's
117     *             definition, or if the provided name is empty.
118     */
119    public static <C extends ConfigurationClient, S extends Configuration> Reference<C, S> parseName(
120        ManagedObjectPath<?, ?> p, InstantiableRelationDefinition<C, S> rd, String s) {
121        // Sanity checks.
122        AbstractManagedObjectDefinition<?, ?> d = p.getManagedObjectDefinition();
123        RelationDefinition<?, ?> tmp = d.getRelationDefinition(rd.getName());
124        if (tmp != rd) {
125            throw new IllegalArgumentException("The relation \"" + rd.getName()
126                + "\" is not associated with the definition \"" + d.getName() + "\"");
127        }
128
129        if (s.trim().length() == 0) {
130            throw new IllegalArgumentException("Empty names are not allowed");
131        }
132
133        return new Reference<>(p, rd, s);
134    }
135
136    /** The name of the referenced managed object. */
137    private final String name;
138
139    /** The path of the referenced managed object. */
140    private final ManagedObjectPath<C, S> path;
141
142    /**
143     * The instantiable relation in the parent which contains the
144     * referenced managed object.
145     */
146    private final InstantiableRelationDefinition<C, S> relation;
147
148    /** Private constructor. */
149    private Reference(ManagedObjectPath<?, ?> parent, InstantiableRelationDefinition<C, S> relation, String name) {
150        this.relation = relation;
151        this.name = name;
152        this.path = parent.child(relation, name);
153    }
154
155    /**
156     * Gets the name of the referenced managed object.
157     *
158     * @return Returns the name of the referenced managed object.
159     */
160    public String getName() {
161        return name;
162    }
163
164    /**
165     * Gets the normalized name of the referenced managed object.
166     *
167     * @return Returns the normalized name of the referenced managed object.
168     */
169    public String getNormalizedName() {
170        PropertyDefinition<?> pd = relation.getNamingPropertyDefinition();
171        return normalizeName(pd);
172    }
173
174    /**
175     * Gets the DN of the referenced managed object.
176     *
177     * @return Returns the DN of the referenced managed object.
178     */
179    public DN toDN() {
180        return path.toDN();
181    }
182
183    /** {@inheritDoc} */
184    public String toString() {
185        return name;
186    }
187
188    /**
189     * Normalize a value using the specified naming property definition
190     * if defined.
191     */
192    private <T> String normalizeName(PropertyDefinition<T> pd) {
193        if (pd != null) {
194            try {
195                // TODO : is it correct to have no validation ?
196                T tvalue = pd.decodeValue(name);
197                return pd.normalizeValue(tvalue);
198            } catch (PropertyException e) {
199                // Fall through to default normalization.
200            }
201        }
202
203        // FIXME: should really use directory string normalizer.
204        String s = name.trim().replaceAll(" +", " ");
205        return toLowerCase(s);
206    }
207}