001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 * 
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 * 
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 * 
019 */
020package org.apache.directory.api.ldap.model.entry;
021
022
023import org.apache.directory.api.i18n.I18n;
024import org.apache.directory.api.ldap.model.exception.LdapException;
025import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
026import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
027import org.apache.directory.api.ldap.model.schema.AttributeType;
028import org.apache.directory.api.ldap.model.schema.LdapComparator;
029import org.apache.directory.api.ldap.model.schema.LdapSyntax;
030import org.apache.directory.api.ldap.model.schema.MatchingRule;
031import org.apache.directory.api.ldap.model.schema.Normalizer;
032import org.apache.directory.api.ldap.model.schema.SyntaxChecker;
033import org.slf4j.Logger;
034import org.slf4j.LoggerFactory;
035
036
037/**
038 * A wrapper around byte[] values in entries.
039 * 
040 * @param <T> The valye type
041 * 
042 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
043 */
044public abstract class AbstractValue<T> implements Value<T>
045{
046    /** logger for reporting errors that might not be handled properly upstream */
047    protected static final Logger LOG = LoggerFactory.getLogger( AbstractValue.class );
048
049    /** reference to the attributeType zssociated with the value */
050    protected transient AttributeType attributeType;
051
052    /** the User Provided value */
053    protected T upValue;
054
055    /** the canonical representation of the user provided value */
056    protected T normalizedValue;
057
058    /** The computed hashcode. We don't want to compute it each time the hashcode() method is called */
059    protected volatile int h;
060
061
062    /**
063     * {@inheritDoc}
064     */
065    @SuppressWarnings("unchecked")
066    @Override
067    public Value<T> clone()
068    {
069        try
070        {
071            return ( Value<T> ) super.clone();
072        }
073        catch ( CloneNotSupportedException cnse )
074        {
075            // Do nothing
076            return null;
077        }
078    }
079
080
081    /**
082     * {@inheritDoc}
083     */
084    @Override
085    public T getReference()
086    {
087        return upValue;
088    }
089
090
091    /**
092     * Get the wrapped value as a String.
093     *
094     * @return the wrapped value as a String
095     */
096    @Override
097    public String getString()
098    {
099        throw new UnsupportedOperationException( "Cannot call this method on a binary value" );
100    }
101
102
103    /**
104     * Get the wrapped value as a byte[].
105     *
106     * @return the wrapped value as a byte[]
107     */
108    @Override
109    public byte[] getBytes()
110    {
111        throw new UnsupportedOperationException( "Cannot call this method on a String value" );
112    }
113
114
115    /**
116     * {@inheritDoc}
117     */
118    @Override
119    public AttributeType getAttributeType()
120    {
121        return attributeType;
122    }
123
124
125    /**
126     * Apply an AttributeType to the current Value, normalizing it.
127     *
128     * @param attributeType The AttributeType to apply
129     * @throws LdapInvalidAttributeValueException If the value is not valid accordingly
130     * to the schema
131     */
132    @SuppressWarnings("unchecked")
133    @Override
134    public void apply( AttributeType attributeType ) throws LdapInvalidAttributeValueException
135    {
136        if ( this.attributeType != null )
137        {
138            // We already have applied an AttributeType, get out
139            LOG.warn( "AttributeType {0} already applied", attributeType.getName() );
140            return;
141        }
142        
143        if ( attributeType == null )
144        {
145            // No attributeType : the normalized value and the user provided value are the same
146            normalizedValue = upValue;
147            return;
148        }
149
150        this.attributeType = attributeType;
151
152        // We first have to normalize the value before we can check its syntax
153        // Get the equality matchingRule, if we have one
154        MatchingRule equality = attributeType.getEquality();
155
156        if ( equality != null )
157        {
158            // If we have an Equality MR, we *must* have a normalizer
159            Normalizer normalizer = equality.getNormalizer();
160
161            if ( normalizer != null )
162            {
163                if ( upValue != null )
164                {
165                    boolean isHR = true;
166                    // Some broken LDAP servers do not have proper syntax definitions
167                    if ( attributeType.getSyntax() != null )
168                    {
169                        isHR = attributeType.getSyntax().isHumanReadable();
170                    }
171                    
172
173                    if ( isHR != isHumanReadable() )
174                    {
175                        
176                        String message = "The '" + attributeType.getName() + "' AttributeType and values must "
177                            + "both be String or binary";
178                        LOG.error( message );
179                        throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, message );
180                    }
181
182                    try
183                    {
184                        if ( isHumanReadable() )
185                        {
186                            if ( normalizedValue != null )
187                            {    
188                                normalizedValue = ( T ) normalizer.normalize( ( String ) normalizedValue );
189                            }
190                            else
191                            {
192                                normalizedValue = ( T ) normalizer.normalize( ( String ) upValue );
193                            }
194                        }
195                        else
196                        {
197                            normalizedValue = ( T ) normalizer.normalize( new BinaryValue( ( byte[] ) upValue ) )
198                                .getNormReference();
199                        }
200                    }
201                    catch ( LdapException ne )
202                    {
203                        String message = I18n.err( I18n.ERR_04447_CANNOT_NORMALIZE_VALUE, ne.getLocalizedMessage() );
204                        LOG.info( message );
205                    }
206                }
207            }
208            else
209            {
210                String message = "The '" + attributeType.getName() + "' AttributeType does not have" + " a normalizer";
211                LOG.error( message );
212                throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, message );
213            }
214        }
215        else
216        {
217            // No MatchingRule, there is nothing we can do but make the normalized value
218            // to be a reference on the user provided value
219            normalizedValue = upValue;
220        }
221
222        // and checks that the value syntax is valid
223        if ( !attributeType.isRelaxed() )
224        {
225            try
226            {
227                LdapSyntax syntax = attributeType.getSyntax();
228    
229                // Check the syntax if not in relaxed mode
230                if ( ( syntax != null ) && ( !isValid( syntax.getSyntaxChecker() ) ) )
231                {
232                    String message = I18n.err( I18n.ERR_04473_NOT_VALID_VALUE, upValue, attributeType );
233                    LOG.info( message );
234                    throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, message );
235                }
236            }
237            catch ( LdapException le )
238            {
239                String message = I18n.err( I18n.ERR_04447_CANNOT_NORMALIZE_VALUE, le.getLocalizedMessage() );
240                LOG.info( message );
241                throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, message, le );
242            }
243        }
244
245        // Rehash the Value now
246        h = 0;
247        hashCode();
248    }
249
250
251    /**
252     * Gets a comparator using getMatchingRule() to resolve the matching
253     * that the comparator is extracted from.
254     *
255     * @return a comparator associated with the attributeType or null if one cannot be found
256     * @throws LdapException if resolution of schema entities fail
257     */
258    @SuppressWarnings("unchecked")
259    protected final LdapComparator<T> getLdapComparator() throws LdapException
260    {
261        if ( attributeType != null )
262        {
263            MatchingRule mr = attributeType.getEquality();
264
265            if ( mr != null )
266            {
267                return ( LdapComparator<T> ) mr.getLdapComparator();
268            }
269        }
270
271        return null;
272    }
273
274
275    /**
276     * {@inheritDoc}
277     */
278    @Override
279    public boolean isInstanceOf( AttributeType attributeType )
280    {
281        return ( attributeType != null )
282            && ( this.attributeType.equals( attributeType ) || this.attributeType.isDescendantOf( attributeType ) );
283    }
284
285
286    /**
287     * {@inheritDoc}
288     */
289    @Override
290    public T getNormReference()
291    {
292        if ( isNull() )
293        {
294            return null;
295        }
296
297        if ( normalizedValue == null )
298        {
299            return upValue;
300        }
301
302        return normalizedValue;
303    }
304
305
306    /**
307     * {@inheritDoc}
308     */
309    @Override
310    public final boolean isNull()
311    {
312        return upValue == null;
313    }
314
315
316    /**
317     * {@inheritDoc}
318     */
319    @Override
320    public final boolean isValid( SyntaxChecker syntaxChecker ) throws LdapInvalidAttributeValueException
321    {
322        if ( syntaxChecker == null )
323        {
324            String message = I18n.err( I18n.ERR_04139, toString() );
325            LOG.error( message );
326            throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, message );
327        }
328
329        if ( ( attributeType != null ) && attributeType.isRelaxed() ) 
330        {
331            return true;
332        }
333        else
334        { 
335            return syntaxChecker.isValidSyntax( normalizedValue );
336        }
337    }
338
339
340    /**
341     * {@inheritDoc}
342     */
343    @Override
344    public final boolean isSchemaAware()
345    {
346        return attributeType != null;
347    }
348}