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    package org.apache.directory.shared.ldap.entry;
020    
021    
022    import java.io.Externalizable;
023    import java.io.IOException;
024    import java.io.ObjectInput;
025    import java.io.ObjectOutput;
026    import java.util.Arrays;
027    import java.util.Comparator;
028    
029    import org.apache.directory.shared.ldap.exception.LdapException;
030    
031    import org.apache.directory.shared.i18n.I18n;
032    import org.apache.directory.shared.ldap.schema.AttributeType;
033    import org.apache.directory.shared.ldap.schema.LdapComparator;
034    import org.apache.directory.shared.ldap.schema.Normalizer;
035    import org.apache.directory.shared.ldap.schema.comparators.ByteArrayComparator;
036    import org.apache.directory.shared.ldap.util.StringTools;
037    import org.slf4j.Logger;
038    import org.slf4j.LoggerFactory;
039    
040    
041    /**
042     * A server side schema aware wrapper around a binary attribute value.
043     * This value wrapper uses schema information to syntax check values,
044     * and to compare them for equality and ordering.  It caches results
045     * and invalidates them when the wrapped value changes.
046     *
047     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
048     * @version $Rev$, $Date$
049     */
050    public class BinaryValue extends AbstractValue<byte[]>
051    {
052        /** Used for serialization */
053        protected static final long serialVersionUID = 2L;
054        
055        /** logger for reporting errors that might not be handled properly upstream */
056        protected static final Logger LOG = LoggerFactory.getLogger( BinaryValue.class );
057        
058        /**
059         * Creates a BinaryValue without an initial wrapped value.
060         *
061         * @param attributeType the schema type associated with this BinaryValue
062         */
063        public BinaryValue()
064        {
065            wrappedValue = null;
066            normalized = false;
067            valid = null;
068            normalizedValue = null;
069        }
070        
071        
072        /**
073         * Creates a BinaryValue without an initial wrapped value.
074         *
075         * @param attributeType the schema type associated with this BinaryValue
076         */
077        public BinaryValue( AttributeType attributeType )
078        {
079            if ( attributeType == null )
080            {
081                throw new IllegalArgumentException( I18n.err( I18n.ERR_04442 ) );
082            }
083    
084            if ( attributeType.getSyntax() == null )
085            {
086                throw new IllegalArgumentException( I18n.err( I18n.ERR_04445 ) );
087            }
088    
089            if ( attributeType.getSyntax().isHumanReadable() )
090            {
091                LOG.warn( "Treating a value of a human readible attribute {} as binary: ", attributeType.getName() );
092            }
093    
094            this.attributeType = attributeType;
095        }
096    
097    
098        /**
099         * Creates a BinaryValue with an initial wrapped binary value.
100         *
101         * @param attributeType the schema type associated with this BinaryValue
102         * @param value the binary value to wrap which may be null, or a zero length byte array
103         */
104        public BinaryValue( byte[] value )
105        {
106            if ( value != null )
107            {
108                this.wrappedValue = new byte[ value.length ];
109                System.arraycopy( value, 0, this.wrappedValue, 0, value.length );
110            }
111            else
112            {
113                this.wrappedValue = null;
114            }
115            
116            normalized = false;
117            valid = null;
118            normalizedValue = null;
119        }
120        
121        
122        /**
123         * Creates a BinaryValue with an initial wrapped binary value.
124         *
125         * @param attributeType the schema type associated with this BinaryValue
126         * @param value the binary value to wrap which may be null, or a zero length byte array
127         */
128        public BinaryValue( AttributeType attributeType, byte[] value )
129        {
130            this( attributeType );
131            this.wrappedValue = value;
132        }
133    
134    
135        /**
136         * Gets a direct reference to the normalized representation for the
137         * wrapped value of this ServerValue wrapper. Implementations will most
138         * likely leverage the attributeType this value is associated with to
139         * determine how to properly normalize the wrapped value.
140         *
141         * @return the normalized version of the wrapped value
142         * @throws LdapException if schema entity resolution fails or normalization fails
143         */
144        public byte[] getNormalizedValueCopy()
145        {
146            if ( isNull() )
147            {
148                return null;
149            }
150    
151            if ( !normalized )
152            {      
153                try
154                {
155                    normalize();
156                }
157                catch ( LdapException ne )
158                {
159                    String message = "Cannot normalize the value :" + ne.getLocalizedMessage();
160                    LOG.warn( message );
161                    normalized = false;
162                }
163            }
164    
165            if ( normalizedValue != null )
166            {
167                byte[] copy = new byte[ normalizedValue.length ];
168                System.arraycopy( normalizedValue, 0, copy, 0, normalizedValue.length );
169                return copy;
170            }
171            else
172            {
173                return StringTools.EMPTY_BYTES;
174            }
175        }
176        
177        
178        /**
179         * Gets the normalized (canonical) representation for the wrapped string.
180         * If the wrapped String is null, null is returned, otherwise the normalized
181         * form is returned.  If no the normalizedValue is null, then this method
182         * will attempt to generate it from the wrapped value: repeated calls to
183         * this method do not unnecessarily normalize the wrapped value.  Only changes
184         * to the wrapped value result in attempts to normalize the wrapped value.
185         *
186         * @return a reference to the normalized version of the wrapped value
187         */
188        public byte[] getNormalizedValueReference()
189        {
190            if ( isNull() )
191            {
192                return null;
193            }
194    
195            if ( !isNormalized() )
196            {
197                try
198                {
199                    normalize();
200                }
201                catch ( LdapException ne )
202                {
203                    String message = "Cannot normalize the value :" + ne.getLocalizedMessage();
204                    LOG.warn( message );
205                    normalized = false;
206                }
207            }
208    
209            if ( normalizedValue != null )
210            {
211                return normalizedValue;
212            }
213            else
214            {
215                return wrappedValue;
216            }
217        }
218    
219        
220        /**
221         * Gets the normalized (canonical) representation for the wrapped byte[].
222         * If the wrapped byte[] is null, null is returned, otherwise the normalized
223         * form is returned.  If the normalizedValue is null, then this method
224         * will attempt to generate it from the wrapped value: repeated calls to
225         * this method do not unnecessarily normalize the wrapped value.  Only changes
226         * to the wrapped value result in attempts to normalize the wrapped value.
227         *
228         * @return gets the normalized value
229         */
230        public byte[] getNormalizedValue()
231        {
232            return getNormalizedValueCopy();
233        }
234    
235    
236        /**
237         * Normalize the value. For a client String value, applies the given normalizer.
238         * 
239         * It supposes that the client has access to the schema in order to select the
240         * appropriate normalizer.
241         * 
242         * @param Normalizer The normalizer to apply to the value
243         * @exception LdapException If the value cannot be normalized
244         */
245        public final void normalize( Normalizer normalizer ) throws LdapException
246        {
247            if ( normalizer != null )
248            {
249                if ( wrappedValue == null )
250                {
251                    normalizedValue = wrappedValue;
252                    normalized = true;
253                    same = true;
254                }
255                else
256                {
257                    normalizedValue = normalizer.normalize( this ).getBytes();
258                    normalized = true;
259                    same = Arrays.equals( wrappedValue, normalizedValue );
260                }
261            }
262            else
263            {
264                normalizedValue = wrappedValue;
265                normalized = false;
266                same = true;
267            }
268        }
269    
270        
271        /**
272         * {@inheritDoc}
273         */
274        public void normalize() throws LdapException
275        {
276            if ( isNormalized() )
277            {
278                // Bypass the normalization if it has already been done. 
279                return;
280            }
281    
282            if ( attributeType != null )
283            {
284                Normalizer normalizer = getNormalizer();
285                normalize( normalizer );
286            }
287            else
288            {
289                normalizedValue = wrappedValue;
290                normalized = true;
291                same = true;
292            }
293        }
294    
295        
296        /**
297         *
298         * @see ServerValue#compareTo(ServerValue)
299         * @throws IllegalStateException on failures to extract the comparator, or the
300         * normalizers needed to perform the required comparisons based on the schema
301         */
302        public int compareTo( Value<byte[]> value )
303        {
304            if ( isNull() )
305            {
306                if ( ( value == null ) || value.isNull() )
307                {
308                    return 0;
309                }
310                else
311                {
312                    return -1;
313                }
314            }
315            else
316            {
317                if ( ( value == null ) || value.isNull() ) 
318                {
319                    return 1;
320                }
321            }
322    
323            BinaryValue binaryValue = ( BinaryValue ) value;
324            
325            if ( attributeType != null )
326            {
327                try
328                {
329                    LdapComparator<byte[]> comparator = getLdapComparator();
330    
331                    if ( comparator != null )
332                    {
333                        return comparator
334                            .compare( getNormalizedValueReference(), binaryValue.getNormalizedValueReference() );
335                    }
336                    else
337                    {
338                        return new ByteArrayComparator( null ).compare( getNormalizedValueReference(), binaryValue
339                            .getNormalizedValueReference() );
340                    }
341                }
342                catch ( LdapException e )
343                {
344                    String msg = I18n.err( I18n.ERR_04443, Arrays.toString( getReference() ), value );
345                    LOG.error( msg, e );
346                    throw new IllegalStateException( msg, e );
347                }
348            }
349            else
350            {
351                return new ByteArrayComparator( null ).compare( getNormalizedValue(), binaryValue.getNormalizedValue() );
352            }
353        }
354    
355    
356        // -----------------------------------------------------------------------
357        // Object Methods
358        // -----------------------------------------------------------------------
359    
360    
361        /**
362         * @see Object#hashCode()
363         * @return the instance's hashcode 
364         */
365        public int hashCode()
366        {
367            // return zero if the value is null so only one null value can be
368            // stored in an attribute - the string version does the same
369            if ( isNull() )
370            {
371                return 0;
372            }
373            
374            byte[] normalizedValue = getNormalizedValueReference();
375            int h = Arrays.hashCode( normalizedValue );
376    
377            return h;
378        }
379    
380    
381        /**
382         * Checks to see if this BinaryValue equals the supplied object.
383         *
384         * This equals implementation overrides the BinaryValue implementation which
385         * is not schema aware.
386         * @throws IllegalStateException on failures to extract the comparator, or the
387         * normalizers needed to perform the required comparisons based on the schema
388         */
389        public boolean equals( Object obj )
390        {
391            if ( this == obj )
392            {
393                return true;
394            }
395            
396            if ( ! ( obj instanceof BinaryValue ) )
397            {
398                return false;
399            }
400    
401            BinaryValue other = ( BinaryValue ) obj;
402            
403            if ( isNull() )
404            {
405                return other.isNull();
406            }
407            
408            // If we have an attributeType, it must be equal
409            // We should also use the comparator if we have an AT
410            if ( attributeType != null )
411            {
412                if ( other.attributeType != null )
413                {
414                    if ( !attributeType.equals( other.attributeType ) )
415                    {
416                        return false;
417                    }
418                }
419                else
420                {
421                    other.attributeType = attributeType;
422                }
423            }
424            else if ( other.attributeType != null )
425            {
426                attributeType = other.attributeType;
427            }
428    
429            // Shortcut : if the values are equals, no need to compare
430            // the normalized values
431            if ( Arrays.equals( wrappedValue, other.wrappedValue ) )
432            {
433                return true;
434            }
435    
436            if ( attributeType != null )
437            {
438                // We have an AttributeType, we eed to use the comparator
439                try
440                {
441                    Comparator<byte[]> comparator = (Comparator<byte[]>)getLdapComparator();
442    
443                    // Compare normalized values
444                    if ( comparator == null )
445                    {
446                        return Arrays.equals( getNormalizedValueReference(), other.getNormalizedValueReference() );
447                    }
448                    else
449                    {
450                        return comparator.compare( getNormalizedValueReference(), other.getNormalizedValueReference() ) == 0;
451                    }
452                }
453                catch ( LdapException ne )
454                {
455                    return false;
456                }
457    
458            }
459            else
460            {
461                // now unlike regular values we have to compare the normalized values
462                return Arrays.equals( getNormalizedValueReference(), other.getNormalizedValueReference() );
463            }
464        }
465    
466    
467        // -----------------------------------------------------------------------
468        // Private Helper Methods (might be put into abstract base class)
469        // -----------------------------------------------------------------------
470        /**
471         * @return a copy of the current value
472         */
473        public BinaryValue clone()
474        {
475            BinaryValue clone = (BinaryValue)super.clone();
476            
477            if ( normalizedValue != null )
478            {
479                clone.normalizedValue = new byte[ normalizedValue.length ];
480                System.arraycopy( normalizedValue, 0, clone.normalizedValue, 0, normalizedValue.length );
481            }
482            
483            if ( wrappedValue != null )
484            {
485                clone.wrappedValue = new byte[ wrappedValue.length ];
486                System.arraycopy( wrappedValue, 0, clone.wrappedValue, 0, wrappedValue.length );
487            }
488            
489            return clone;
490        }
491    
492    
493        /**
494         * {@inheritDoc}
495         */
496        public byte[] get()
497        {
498            if ( wrappedValue == null )
499            {
500                return null;
501            }
502            
503            final byte[] copy = new byte[ wrappedValue.length ];
504            System.arraycopy( wrappedValue, 0, copy, 0, wrappedValue.length );
505            
506            return copy;
507        }
508        
509        
510        /**
511         * Tells if the current value is Binary or String
512         * 
513         * @return <code>true</code> if the value is Binary, <code>false</code> otherwise
514         */
515        public boolean isBinary()
516        {
517            return true;
518        }
519        
520        
521        /**
522         * @return The length of the interned value
523         */
524        public int length()
525        {
526            return wrappedValue != null ? wrappedValue.length : 0;
527        }
528    
529    
530        /**
531         * Get the wrapped value as a byte[]. This method returns a copy of 
532         * the wrapped byte[].
533         * 
534         * @return the wrapped value as a byte[]
535         */
536        public byte[] getBytes()
537        {
538            return get();
539        }
540        
541        
542        /**
543         * Get the wrapped value as a String.
544         *
545         * @return the wrapped value as a String
546         */
547        public String getString()
548        {
549            return StringTools.utf8ToString( wrappedValue );
550        }
551        
552        
553        /**
554         * @see Externalizable#readExternal(ObjectInput)
555         */
556        public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
557        {
558            // Read the wrapped value, if it's not null
559            int wrappedLength = in.readInt();
560            
561            if ( wrappedLength >= 0 )
562            {
563                wrappedValue = new byte[wrappedLength];
564                
565                if ( wrappedLength > 0 )
566                {
567                    in.read( wrappedValue );
568                }
569            }
570            
571            // Read the isNormalized flag
572            normalized = in.readBoolean();
573            
574            if ( normalized )
575            {
576                int normalizedLength = in.readInt();
577                
578                if ( normalizedLength >= 0 )
579                {
580                    normalizedValue = new byte[normalizedLength];
581                    
582                    if ( normalizedLength > 0 )
583                    {
584                        in.read( normalizedValue );
585                    }
586                }
587            }
588        }
589    
590        
591        /**
592         * @see Externalizable#writeExternal(ObjectOutput)
593         */
594        public void writeExternal( ObjectOutput out ) throws IOException
595        {
596            // Write the wrapped value, if it's not null
597            if ( wrappedValue != null )
598            {
599                out.writeInt( wrappedValue.length );
600                
601                if ( wrappedValue.length > 0 )
602                {
603                    out.write( wrappedValue, 0, wrappedValue.length );
604                }
605            }
606            else
607            {
608                out.writeInt( -1 );
609            }
610            
611            // Write the isNormalized flag
612            if ( normalized )
613            {
614                out.writeBoolean( true );
615                
616                // Write the normalized value, if not null
617                if ( normalizedValue != null )
618                {
619                    out.writeInt( normalizedValue.length );
620                    
621                    if ( normalizedValue.length > 0 )
622                    {
623                        out.write( normalizedValue, 0, normalizedValue.length );
624                    }
625                }
626                else
627                {
628                    out.writeInt( -1 );
629                }
630            }
631            else
632            {
633                out.writeBoolean( false );
634            }
635        }
636        
637        
638        /**
639         * We will write the value and the normalized value, only
640         * if the normalized value is different.
641         * 
642         * If the value is empty, a flag is written at the beginning with 
643         * the value true, otherwise, a false is written.
644         * 
645         * The data will be stored following this structure :
646         *  [length] the wrapped length. Can be -1, if wrapped is null
647         *  [value length]
648         *  [UP value] if not empty
649         *  [normalized] (will be false if the value can't be normalized)
650         *  [same] (a flag set to true if the normalized value equals the UP value)
651         *  [Norm value] (the normalized value if different from the UP value, and not empty)
652         *  
653         *  @param out the buffer in which we will stored the serialized form of the value
654         *  @throws IOException if we can't write into the buffer
655         */
656        public void serialize( ObjectOutput out ) throws IOException
657        {
658            if ( wrappedValue != null )
659            {
660                // write a the wrapped length
661                out.writeInt( wrappedValue.length );
662    
663                // Write the data if not empty
664                if ( wrappedValue.length > 0 )
665                {
666                    // The data
667                    out.write( wrappedValue );
668    
669                    // Normalize the data
670                    try
671                    {
672                        normalize();
673    
674                        if ( !normalized )
675                        {
676                            // We may not have a normalizer. Just get out
677                            // after having writen the flag
678                            out.writeBoolean( false );
679                        }
680                        else
681                        {
682                            // Write a flag indicating that the data has been normalized
683                            out.writeBoolean( true );
684    
685                            if ( Arrays.equals( getReference(), normalizedValue ) )
686                            {
687                                // Write the 'same = true' flag
688                                out.writeBoolean( true );
689                            }
690                            else
691                            {
692                                // Write the 'same = false' flag
693                                out.writeBoolean( false );
694    
695                                // Write the normalized value length
696                                out.writeInt( normalizedValue.length );
697    
698                                if ( normalizedValue.length > 0 )
699                                {
700                                    // Write the normalized value if not empty
701                                    out.write( normalizedValue );
702                                }
703                            }
704                        }
705                    }
706                    catch ( LdapException ne )
707                    {
708                        // The value can't be normalized, we don't write the 
709                        // normalized value.
710                        normalizedValue = null;
711                        out.writeBoolean( false );
712                    }
713                }
714            }
715            else
716            {
717                // Write -1 indicating that the value is null
718                out.writeInt( -1 );
719            }
720        }
721    
722    
723        /**
724         * 
725         * Deserialize a BinaryValue. 
726         *
727         * @param in the buffer containing the bytes with the serialized value
728         * @throws IOException 
729         * @throws ClassNotFoundException
730         */
731        public void deserialize( ObjectInput in ) throws IOException, ClassNotFoundException
732        {
733            // The UP value length
734            int wrappedLength = in.readInt();
735    
736            if ( wrappedLength == -1 )
737            {
738                // If the value is null, the length will be set to -1
739                same = true;
740                wrappedValue = null;
741            }
742            else if ( wrappedLength == 0 )
743            {
744                wrappedValue = StringTools.EMPTY_BYTES;
745                same = true;
746                normalized = true;
747                normalizedValue = wrappedValue;
748            }
749            else
750            {
751                wrappedValue = new byte[wrappedLength];
752    
753                // Read the data
754                in.readFully( wrappedValue );
755    
756                // Check if we have a normalized value
757                normalized = in.readBoolean();
758    
759                if ( normalized )
760                {
761                    // Read the 'same' flag
762                    same = in.readBoolean();
763    
764                    if ( !same )
765                    {
766                        // Read the normalizedvalue length
767                        int normalizedLength = in.readInt();
768    
769                        if ( normalizedLength > 0 )
770                        {
771                            normalizedValue = new byte[normalizedLength];
772    
773                            // Read the normalized value
774                            in.read( normalizedValue, 0, normalizedLength );
775                        }
776                        else
777                        {
778                            normalizedValue = StringTools.EMPTY_BYTES;
779                        }
780                    }
781                    else
782                    {
783                        normalizedValue = new byte[wrappedLength];
784                        System.arraycopy( wrappedValue, 0, normalizedValue, 0, wrappedLength );
785                    }
786                }
787            }
788        }
789        
790        
791        /**
792         * Dumps binary in hex with label.
793         *
794         * @see Object#toString()
795         */
796        public String toString()
797        {
798            if ( wrappedValue == null )
799            {
800                return "null";
801            }
802            else if ( wrappedValue.length > 16 )
803            {
804                // Just dump the first 16 bytes...
805                byte[] copy = new byte[16];
806                
807                System.arraycopy( wrappedValue, 0, copy, 0, 16 );
808                
809                return "'" + StringTools.dumpBytes( copy ) + "...'";
810            }
811            else
812            {
813                return "'" + StringTools.dumpBytes( wrappedValue ) + "'";
814            }
815        }
816    }