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 */
019package org.apache.directory.api.ldap.model.entry;
020
021
022import java.io.IOException;
023import java.io.ObjectInput;
024import java.io.ObjectOutput;
025import java.util.Arrays;
026import java.util.Comparator;
027
028import org.apache.directory.api.i18n.I18n;
029import org.apache.directory.api.ldap.model.exception.LdapException;
030import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
031import org.apache.directory.api.ldap.model.schema.AttributeType;
032import org.apache.directory.api.ldap.model.schema.LdapComparator;
033import org.apache.directory.api.ldap.model.schema.MatchingRule;
034import org.apache.directory.api.ldap.model.schema.Normalizer;
035import org.apache.directory.api.ldap.model.schema.comparators.ByteArrayComparator;
036import org.apache.directory.api.util.Strings;
037
038
039/**
040 * A server side schema aware wrapper around a binary attribute value.
041 * This value wrapper uses schema information to syntax check values,
042 * and to compare them for equality and ordering.  It caches results
043 * and invalidates them when the user provided value changes.
044 *
045 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
046 */
047public class BinaryValue extends AbstractValue<byte[]>
048{
049    /** Used for serialization */
050    public static final long serialVersionUID = 2L;
051
052
053    /**
054     * Creates a BinaryValue without an initial user provided value.
055     *
056     * @param attributeType the schema type associated with this BinaryValue
057     */
058    /* No protection */BinaryValue( AttributeType attributeType )
059    {
060        if ( attributeType != null )
061        {
062            // We must have a Syntax
063            if ( attributeType.getSyntax() == null )
064            {
065                throw new IllegalArgumentException( I18n.err( I18n.ERR_04445 ) );
066            }
067
068            if ( attributeType.getSyntax().isHumanReadable() )
069            {
070                LOG.warn( "Treating a value of a human readible attribute {} as binary: ", attributeType.getName() );
071            }
072
073            this.attributeType = attributeType;
074        }
075    }
076
077
078    /**
079     * Creates a BinaryValue with an initial user provided binary value.
080     *
081     * @param value the binary value to wrap which may be null, or a zero length byte array
082     */
083    public BinaryValue( byte[] value )
084    {
085        if ( value != null )
086        {
087            this.upValue = new byte[value.length];
088            this.normalizedValue = new byte[value.length];
089            System.arraycopy( value, 0, this.upValue, 0, value.length );
090            System.arraycopy( value, 0, this.normalizedValue, 0, value.length );
091        }
092        else
093        {
094            this.upValue = null;
095            this.normalizedValue = null;
096        }
097    }
098
099
100    /**
101     * Creates a BinaryValue with an initial user provided binary value.
102     *
103     * @param attributeType the schema type associated with this BinaryValue
104     * @param value the binary value to wrap which may be null, or a zero length byte array
105     * @throws LdapInvalidAttributeValueException If the added value is invalid accordingly 
106     * to the schema
107     */
108    public BinaryValue( AttributeType attributeType, byte[] value ) throws LdapInvalidAttributeValueException
109    {
110        this( value );
111        apply( attributeType );
112    }
113
114
115    /**
116     * Gets a direct reference to the normalized representation for the
117     * user provided value of this ServerValue wrapper. Implementations will most
118     * likely leverage the attributeType this value is associated with to
119     * determine how to properly normalize the user provided value.
120     *
121     * @return the normalized version of the user provided value
122     */
123    @Override
124    public byte[] getNormValue()
125    {
126        if ( isNull() )
127        {
128            return null;
129        }
130
131        byte[] copy = new byte[normalizedValue.length];
132        System.arraycopy( normalizedValue, 0, copy, 0, normalizedValue.length );
133        return copy;
134    }
135
136
137    /**
138     * Compare the current value with a provided one
139     *
140     * @param value The value we want to compare to
141     * @return -1 if the current is below the provided one, 1 if it's above, 0 if they are equal
142     */
143    @Override
144    public int compareTo( Value<byte[]> value )
145    {
146        if ( isNull() )
147        {
148            if ( ( value == null ) || value.isNull() )
149            {
150                return 0;
151            }
152            else
153            {
154                return -1;
155            }
156        }
157        else
158        {
159            if ( ( value == null ) || value.isNull() )
160            {
161                return 1;
162            }
163        }
164
165        BinaryValue binaryValue = ( BinaryValue ) value;
166
167        if ( attributeType != null )
168        {
169            try
170            {
171                LdapComparator<byte[]> comparator = getLdapComparator();
172
173                if ( comparator != null )
174                {
175                    return comparator
176                        .compare( getNormReference(), binaryValue.getNormReference() );
177                }
178                else
179                {
180                    return new ByteArrayComparator( null ).compare( getNormReference(), binaryValue
181                        .getNormReference() );
182                }
183            }
184            catch ( LdapException e )
185            {
186                String msg = I18n.err( I18n.ERR_04443, Arrays.toString( getReference() ), value );
187                LOG.error( msg, e );
188                throw new IllegalStateException( msg, e );
189            }
190        }
191        else
192        {
193            return new ByteArrayComparator( null ).compare( getNormValue(), binaryValue.getNormValue() );
194        }
195    }
196
197
198    // -----------------------------------------------------------------------
199    // Object Methods
200    // -----------------------------------------------------------------------
201    /**
202     * @see Object#hashCode()
203     * @return the instance's hashcode 
204     */
205    @Override
206    public int hashCode()
207    {
208        if ( h == 0 )
209        {
210            // return zero if the value is null so only one null value can be
211            // stored in an attribute - the string version does the same
212            if ( isNull() )
213            {
214                return 0;
215            }
216
217            byte[] normalizedValue = getNormReference();
218            h = Arrays.hashCode( normalizedValue );
219        }
220
221        return h;
222    }
223
224
225    /**
226     * Checks to see if this BinaryValue equals the supplied object.
227     *
228     * This equals implementation overrides the BinaryValue implementation which
229     * is not schema aware.
230     */
231    @Override
232    public boolean equals( Object obj )
233    {
234        if ( this == obj )
235        {
236            return true;
237        }
238
239        if ( !( obj instanceof BinaryValue ) )
240        {
241            return false;
242        }
243
244        BinaryValue other = ( BinaryValue ) obj;
245
246        // First check if we have an attrbuteType.
247        if ( attributeType != null )
248        {
249            // yes : check for the other value
250            if ( other.attributeType != null )
251            {
252                if ( attributeType.getOid().equals( other.getAttributeType().getOid() ) )
253                {
254                    // Both AttributeType have the same OID, we can assume they are 
255                    // equals. We don't check any further, because the unicity of OID
256                    // makes it unlikely that the two AT are different.
257                    // The values may be both null
258                    if ( isNull() )
259                    {
260                        return other.isNull();
261                    }
262
263                    // Shortcut : if we have an AT for both the values, check the 
264                    // already normalized values
265                    if ( Arrays.equals( upValue, other.upValue ) )
266                    {
267                        return true;
268                    }
269
270                    // We have an AttributeType, we use the associated comparator
271                    try
272                    {
273                        Comparator<byte[]> comparator = getLdapComparator();
274
275                        // Compare normalized values
276                        if ( comparator == null )
277                        {
278                            return Arrays.equals( getNormReference(), other.getNormReference() );
279                        }
280                        else
281                        {
282                            return comparator.compare( getNormReference(), other.getNormReference() ) == 0;
283                        }
284                    }
285                    catch ( LdapException ne )
286                    {
287                        return false;
288                    }
289                }
290                else
291                {
292                    // We can't compare two values when the two ATs are different
293                    return false;
294                }
295            }
296            else
297            {
298                // We only have one AT : we will assume that both values are for the 
299                // same AT.
300                // The values may be both null
301                if ( isNull() )
302                {
303                    return other.isNull();
304                }
305
306                // We have an AttributeType on the base value, we need to use its comparator
307                try
308                {
309                    Comparator<byte[]> comparator = getLdapComparator();
310
311                    // Compare normalized values. We have to normalized the other value,
312                    // as it has no AT
313                    MatchingRule equality = getAttributeType().getEquality();
314
315                    if ( equality == null )
316                    {
317                        // No matching rule : compare the raw values
318                        return Arrays.equals( getNormReference(), other.getNormReference() );
319                    }
320
321                    Normalizer normalizer = equality.getNormalizer();
322
323                    BinaryValue otherValue = ( BinaryValue ) normalizer.normalize( other );
324
325                    if ( comparator == null )
326                    {
327                        return Arrays.equals( getNormReference(), otherValue.getNormReference() );
328                    }
329                    else
330                    {
331                        return comparator.compare( getNormReference(), otherValue.getNormReference() ) == 0;
332                    }
333                }
334                catch ( LdapException ne )
335                {
336                    return false;
337                }
338            }
339        }
340        else
341        {
342            // No : check for the other value
343            if ( other.attributeType != null )
344            {
345                // We only have one AT : we will assume that both values are for the 
346                // same AT.
347                // The values may be both null
348                if ( isNull() )
349                {
350                    return other.isNull();
351                }
352
353                try
354                {
355                    Comparator<byte[]> comparator = other.getLdapComparator();
356
357                    // Compare normalized values. We have to normalized the other value,
358                    // as it has no AT
359                    MatchingRule equality = other.getAttributeType().getEquality();
360
361                    if ( equality == null )
362                    {
363                        // No matching rule : compare the raw values
364                        return Arrays.equals( getNormReference(), other.getNormReference() );
365                    }
366
367                    Normalizer normalizer = equality.getNormalizer();
368
369                    BinaryValue thisValue = ( BinaryValue ) normalizer.normalize( this );
370
371                    if ( comparator == null )
372                    {
373                        return Arrays.equals( thisValue.getNormReference(), other.getNormReference() );
374                    }
375                    else
376                    {
377                        return comparator.compare( thisValue.getNormReference(), other.getNormReference() ) == 0;
378                    }
379                }
380                catch ( LdapException ne )
381                {
382                    return false;
383                }
384            }
385            else
386            {
387                // The values may be both null
388                if ( isNull() )
389                {
390                    return other.isNull();
391                }
392
393                // Now check the normalized values
394                return Arrays.equals( getNormReference(), other.getNormReference() );
395            }
396        }
397    }
398
399
400    // -----------------------------------------------------------------------
401    // Cloneable methods
402    // -----------------------------------------------------------------------
403    /**
404     * {@inheritDoc}
405     */
406    @Override
407    public BinaryValue clone()
408    {
409        BinaryValue clone = ( BinaryValue ) super.clone();
410
411        // We have to copy the byte[], they are just referenced by suoer.clone()
412        if ( normalizedValue != null )
413        {
414            clone.normalizedValue = new byte[normalizedValue.length];
415            System.arraycopy( normalizedValue, 0, clone.normalizedValue, 0, normalizedValue.length );
416        }
417
418        if ( upValue != null )
419        {
420            clone.upValue = new byte[upValue.length];
421            System.arraycopy( upValue, 0, clone.upValue, 0, upValue.length );
422        }
423
424        return clone;
425    }
426
427
428    /**
429     * {@inheritDoc}
430     */
431    @Override
432    public byte[] getValue()
433    {
434        if ( upValue == null )
435        {
436            return null;
437        }
438
439        final byte[] copy = new byte[upValue.length];
440        System.arraycopy( upValue, 0, copy, 0, upValue.length );
441
442        return copy;
443    }
444
445
446    /**
447     * Tells if the current value is Human Readable
448     * 
449     * @return <code>true</code> if the value is HR, <code>false</code> otherwise
450     */
451    @Override
452    public boolean isHumanReadable()
453    {
454        return false;
455    }
456
457
458    /**
459     * @return The length of the interned value
460     */
461    @Override
462    public int length()
463    {
464        return upValue != null ? upValue.length : 0;
465    }
466
467
468    /**
469     * Get the user provided value as a byte[]. This method returns a copy of 
470     * the user provided byte[].
471     * 
472     * @return the user provided value as a byte[]
473     */
474    @Override
475    public byte[] getBytes()
476    {
477        return getValue();
478    }
479
480
481    /**
482     * Get the user provided value as a String.
483     *
484     * @return the user provided value as a String
485     */
486    @Override
487    public String getString()
488    {
489        return Strings.utf8ToString( upValue );
490    }
491
492
493    /**
494     * Deserialize a BinaryValue. It will return a new BinaryValue instance.
495     * 
496     * @param in The input stream
497     * @return A new StringValue instance
498     * @throws IOException If the stream can't be read
499     * @throws ClassNotFoundException If we can't instanciate a BinaryValue
500     */
501    public static BinaryValue deserialize( ObjectInput in ) throws IOException, ClassNotFoundException
502    {
503        BinaryValue value = new BinaryValue( ( AttributeType ) null );
504        value.readExternal( in );
505
506        return value;
507    }
508
509
510    /**
511     * Deserialize a schema aware BinaryValue. It will return a new BinaryValue instance.
512     * 
513     * @param attributeType The AttributeType associated with the Value. Can be null
514     * @param in The input stream
515     * @return A new StringValue instance
516     * @throws IOException If the stream can't be read
517     * @throws ClassNotFoundException If we can't instanciate a BinaryValue
518     */
519    public static BinaryValue deserialize( AttributeType attributeType, ObjectInput in ) throws IOException,
520        ClassNotFoundException
521    {
522        BinaryValue value = new BinaryValue( attributeType );
523        value.readExternal( in );
524
525        return value;
526    }
527
528
529    /**
530     * {@inheritDoc}
531     */
532    @Override
533    public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
534    {
535        // Read the BINARY flag
536        boolean isHR = in.readBoolean();
537
538        if ( isHR )
539        {
540            throw new IOException( "The serialized value is not a Binary value" );
541        }
542        // Read the user provided value, if it's not null
543        int upLength = in.readInt();
544
545        if ( upLength >= 0 )
546        {
547            upValue = new byte[upLength];
548
549            in.readFully( upValue );
550        }
551
552        // Read the isNormalized flag
553        boolean normalized = in.readBoolean();
554
555        if ( normalized )
556        {
557            int normalizedLength = in.readInt();
558
559            if ( normalizedLength >= 0 )
560            {
561                normalizedValue = new byte[normalizedLength];
562
563                in.readFully( normalizedValue );
564            }
565        }
566        else
567        {
568            if ( attributeType != null )
569            {
570                try
571                {
572                    normalizedValue = attributeType.getEquality().getNormalizer().normalize( this ).getBytes();
573                    MatchingRule equality = attributeType.getEquality();
574
575                    if ( equality == null )
576                    {
577                        if ( upLength >= 0 )
578                        {
579                            normalizedValue = new byte[upLength];
580
581                            System.arraycopy( upValue, 0, normalizedValue, 0, upLength );
582                        }
583                    }
584                    else
585                    {
586                        Normalizer normalizer = equality.getNormalizer();
587
588                        if ( normalizer != null )
589                        {
590                            normalizedValue = normalizer.normalize( this ).getBytes();
591                        }
592                        else
593                        {
594                            if ( upLength >= 0 )
595                            {
596                                normalizedValue = new byte[upLength];
597
598                                System.arraycopy( upValue, 0, normalizedValue, 0, upLength );
599                            }
600                        }
601                    }
602                }
603                catch ( LdapException le )
604                {
605                    // Copy the upValue into the normalizedValue
606                    if ( upLength >= 0 )
607                    {
608                        normalizedValue = new byte[upLength];
609
610                        System.arraycopy( upValue, 0, normalizedValue, 0, upLength );
611                    }
612                }
613            }
614            else
615            {
616                // Copy the upValue into the normalizedValue
617                if ( upLength >= 0 )
618                {
619                    normalizedValue = new byte[upLength];
620
621                    System.arraycopy( upValue, 0, normalizedValue, 0, upLength );
622                }
623            }
624        }
625
626        // The hashCoe
627        h = in.readInt();
628    }
629
630
631    /**
632     * {@inheritDoc}
633     */
634    @Override
635    public void writeExternal( ObjectOutput out ) throws IOException
636    {
637        // Write the BINARY flag
638        out.writeBoolean( BINARY );
639
640        // Write the user provided value, if it's not null
641        if ( upValue != null )
642        {
643            out.writeInt( upValue.length );
644
645            if ( upValue.length > 0 )
646            {
647                out.write( upValue, 0, upValue.length );
648            }
649        }
650        else
651        {
652            out.writeInt( -1 );
653        }
654
655        // Write the isNormalized flag
656        if ( attributeType != null )
657        {
658            out.writeBoolean( true );
659
660            // Write the normalized value, if not null
661            if ( normalizedValue != null )
662            {
663                out.writeInt( normalizedValue.length );
664
665                if ( normalizedValue.length > 0 )
666                {
667                    out.write( normalizedValue, 0, normalizedValue.length );
668                }
669            }
670            else
671            {
672                out.writeInt( -1 );
673            }
674        }
675        else
676        {
677            out.writeBoolean( false );
678        }
679
680        // The hashCode
681        out.writeInt( h );
682
683        out.flush();
684    }
685
686
687    /**
688     * Dumps binary in hex with label.
689     *
690     * @see Object#toString()
691     */
692    @Override
693    public String toString()
694    {
695        if ( upValue == null )
696        {
697            return "null";
698        }
699        else if ( upValue.length > 16 )
700        {
701            // Just dump the first 16 bytes...
702            byte[] copy = new byte[16];
703
704            System.arraycopy( upValue, 0, copy, 0, 16 );
705
706            return Strings.dumpBytes( copy ) + "...";
707        }
708        else
709        {
710            return Strings.dumpBytes( upValue );
711        }
712    }
713}