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.client;
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    
028    import javax.naming.NamingException;
029    
030    import org.apache.directory.shared.ldap.NotImplementedException;
031    import org.apache.directory.shared.ldap.entry.AbstractValue;
032    import org.apache.directory.shared.ldap.entry.Value;
033    import org.apache.directory.shared.ldap.schema.Normalizer;
034    import org.apache.directory.shared.ldap.schema.comparators.ByteArrayComparator;
035    import org.apache.directory.shared.ldap.util.StringTools;
036    import org.slf4j.Logger;
037    import org.slf4j.LoggerFactory;
038    
039    
040    /**
041     * A server side schema aware wrapper around a binary attribute value.
042     * This value wrapper uses schema information to syntax check values,
043     * and to compare them for equality and ordering.  It caches results
044     * and invalidates them when the wrapped value changes.
045     *
046     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
047     * @version $Rev$, $Date$
048     */
049    public class ClientBinaryValue extends AbstractValue<byte[]>
050    {
051        /** Used for serialization */
052        private static final long serialVersionUID = 2L;
053        
054        /** logger for reporting errors that might not be handled properly upstream */
055        private static final Logger LOG = LoggerFactory.getLogger( ClientBinaryValue.class );
056    
057    
058        /**
059         * Creates a ServerBinaryValue without an initial wrapped value.
060         *
061         * @param attributeType the schema type associated with this ServerBinaryValue
062         */
063        public ClientBinaryValue()
064        {
065            wrapped = null;
066            normalized = false;
067            valid = null;
068            normalizedValue = null;
069        }
070    
071    
072        /**
073         * Creates a ServerBinaryValue with an initial wrapped binary value.
074         *
075         * @param attributeType the schema type associated with this ServerBinaryValue
076         * @param wrapped the binary value to wrap which may be null, or a zero length byte array
077         */
078        public ClientBinaryValue( byte[] wrapped )
079        {
080            if ( wrapped != null )
081            {
082                this.wrapped = new byte[ wrapped.length ];
083                System.arraycopy( wrapped, 0, this.wrapped, 0, wrapped.length );
084            }
085            else
086            {
087                this.wrapped = null;
088            }
089            
090            normalized = false;
091            valid = null;
092            normalizedValue = null;
093        }
094    
095    
096        // -----------------------------------------------------------------------
097        // Value<String> Methods
098        // -----------------------------------------------------------------------
099        /**
100         * Reset the value
101         */
102        public void clear()
103        {
104            wrapped = null;
105            normalized = false;
106            normalizedValue = null;
107            valid = null;
108        }
109    
110    
111    
112    
113        /*
114         * Sets the wrapped binary value.  Has the side effect of setting the
115         * normalizedValue and the valid flags to null if the wrapped value is
116         * different than what is already set.  These cached values must be
117         * recomputed to be correct with different values.
118         *
119         * @see ServerValue#set(Object)
120         */
121        public final void set( byte[] wrapped )
122        {
123            // Why should we invalidate the normalized value if it's we're setting the
124            // wrapper to it's current value?
125            byte[] value = getReference();
126            
127            if ( value != null )
128            {
129                if ( Arrays.equals( wrapped, value ) )
130                {
131                    return;
132                }
133            }
134    
135            normalizedValue = null;
136            normalized = false;
137            valid = null;
138            
139            if ( wrapped == null )
140            {
141                this.wrapped = null;
142            }
143            else
144            {
145                this.wrapped = new byte[ wrapped.length ];
146                System.arraycopy( wrapped, 0, this.wrapped, 0, wrapped.length );
147            }
148        }
149    
150    
151        // -----------------------------------------------------------------------
152        // ServerValue<String> Methods
153        // -----------------------------------------------------------------------
154        /**
155         * Gets a direct reference to the normalized representation for the
156         * wrapped value of this ServerValue wrapper. Implementations will most
157         * likely leverage the attributeType this value is associated with to
158         * determine how to properly normalize the wrapped value.
159         *
160         * @return the normalized version of the wrapped value
161         * @throws NamingException if schema entity resolution fails or normalization fails
162         */
163        public byte[] getNormalizedValueCopy()
164        {
165            if ( normalizedValue == null )
166            {
167                return null;
168            }
169    
170            byte[] copy = new byte[ normalizedValue.length ];
171            System.arraycopy( normalizedValue, 0, copy, 0, normalizedValue.length );
172            return copy;
173        }
174    
175    
176        /**
177         * Normalize the value. For a client String value, applies the given normalizer.
178         * 
179         * It supposes that the client has access to the schema in order to select the
180         * appropriate normalizer.
181         * 
182         * @param Normalizer The normalizer to apply to the value
183         * @exception NamingException If the value cannot be normalized
184         */
185        public final void normalize( Normalizer normalizer ) throws NamingException
186        {
187            if ( normalizer != null )
188            {
189                if ( wrapped == null )
190                {
191                    normalizedValue = wrapped;
192                    normalized = true;
193                    same = true;
194                }
195                else
196                {
197                    normalizedValue = normalizer.normalize( this ).getBytes();
198                    normalized = true;
199                    same = Arrays.equals( wrapped, normalizedValue );
200                }
201            }
202        }
203    
204        
205        /**
206         *
207         * @see ServerValue#compareTo(ServerValue)
208         * @throws IllegalStateException on failures to extract the comparator, or the
209         * normalizers needed to perform the required comparisons based on the schema
210         */
211        public int compareTo( Value<byte[]> value )
212        {
213            if ( isNull() )
214            {
215                if ( ( value == null ) || value.isNull() )
216                {
217                    return 0;
218                }
219                else
220                {
221                    return -1;
222                }
223            }
224            else
225            {
226                if ( ( value == null ) || value.isNull() ) 
227                {
228                    return 1;
229                }
230            }
231    
232            if ( value instanceof ClientBinaryValue )
233            {
234                ClientBinaryValue binaryValue = ( ClientBinaryValue ) value;
235    
236                return ByteArrayComparator.INSTANCE.compare( getNormalizedValue(), binaryValue.getNormalizedValue() );
237            }
238            
239            String message = "I don't really know how to compare anything other " +
240                "than ServerBinaryValues at this point in time."; 
241            LOG.error( message );
242            throw new NotImplementedException( message );
243        }
244    
245    
246        // -----------------------------------------------------------------------
247        // Object Methods
248        // -----------------------------------------------------------------------
249    
250    
251        /**
252         * @see Object#hashCode()
253         * @return the instance's hashcode 
254         */
255        public int hashCode()
256        {
257            // return zero if the value is null so only one null value can be
258            // stored in an attribute - the string version does the same
259            if ( isNull() )
260            {
261                return 0;
262            }
263    
264            return Arrays.hashCode( getNormalizedValueReference() );
265        }
266    
267    
268        /**
269         * Checks to see if this ServerBinaryValue equals the supplied object.
270         *
271         * This equals implementation overrides the BinaryValue implementation which
272         * is not schema aware.
273         * @throws IllegalStateException on failures to extract the comparator, or the
274         * normalizers needed to perform the required comparisons based on the schema
275         */
276        public boolean equals( Object obj )
277        {
278            if ( this == obj )
279            {
280                return true;
281            }
282            
283            if ( ! ( obj instanceof ClientBinaryValue ) )
284            {
285                return false;
286            }
287    
288            ClientBinaryValue other = ( ClientBinaryValue ) obj;
289            
290            if ( isNull() )
291            {
292                return other.isNull();
293            }
294    
295            // now unlike regular values we have to compare the normalized values
296            return Arrays.equals( getNormalizedValueReference(), other.getNormalizedValueReference() );
297        }
298    
299    
300        // -----------------------------------------------------------------------
301        // Private Helper Methods (might be put into abstract base class)
302        // -----------------------------------------------------------------------
303        /**
304         * @return a copy of the current value
305         */
306        public ClientBinaryValue clone()
307        {
308            ClientBinaryValue clone = (ClientBinaryValue)super.clone();
309            
310            if ( normalizedValue != null )
311            {
312                clone.normalizedValue = new byte[ normalizedValue.length ];
313                System.arraycopy( normalizedValue, 0, clone.normalizedValue, 0, normalizedValue.length );
314            }
315            
316            if ( wrapped != null )
317            {
318                clone.wrapped = new byte[ wrapped.length ];
319                System.arraycopy( wrapped, 0, clone.wrapped, 0, wrapped.length );
320            }
321            
322            return clone;
323        }
324    
325    
326        /**
327         * Gets a copy of the binary value.
328         *
329         * @return a copy of the binary value
330         */
331        public byte[] getCopy()
332        {
333            if ( wrapped == null )
334            {
335                return null;
336            }
337    
338            
339            final byte[] copy = new byte[ wrapped.length ];
340            System.arraycopy( wrapped, 0, copy, 0, wrapped.length );
341            return copy;
342        }
343        
344        
345        /**
346         * Tells if the current value is Binary or String
347         * 
348         * @return <code>true</code> if the value is Binary, <code>false</code> otherwise
349         */
350        public boolean isBinary()
351        {
352            return true;
353        }
354        
355        
356        /**
357         * @return The length of the interned value
358         */
359        public int length()
360        {
361            return wrapped != null ? wrapped.length : 0;
362        }
363    
364    
365        /**
366         * Get the wrapped value as a byte[]. This method returns a copy of 
367         * the wrapped byte[].
368         * 
369         * @return the wrapped value as a byte[]
370         */
371        public byte[] getBytes()
372        {
373            return getCopy();
374        }
375        
376        
377        /**
378         * Get the wrapped value as a String.
379         *
380         * @return the wrapped value as a String
381         */
382        public String getString()
383        {
384            return StringTools.utf8ToString( wrapped );
385        }
386        
387        
388        /**
389         * @see Externalizable#readExternal(ObjectInput)
390         */
391        public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
392        {
393            // Read the wrapped value, if it's not null
394            int wrappedLength = in.readInt();
395            
396            if ( wrappedLength >= 0 )
397            {
398                wrapped = new byte[wrappedLength];
399                
400                if ( wrappedLength > 0 )
401                {
402                    in.read( wrapped );
403                }
404            }
405            
406            // Read the isNormalized flag
407            normalized = in.readBoolean();
408            
409            if ( normalized )
410            {
411                int normalizedLength = in.readInt();
412                
413                if ( normalizedLength >= 0 )
414                {
415                    normalizedValue = new byte[normalizedLength];
416                    
417                    if ( normalizedLength > 0 )
418                    {
419                        in.read( normalizedValue );
420                    }
421                }
422            }
423        }
424    
425        
426        /**
427         * @see Externalizable#writeExternal(ObjectOutput)
428         */
429        public void writeExternal( ObjectOutput out ) throws IOException
430        {
431            // Write the wrapped value, if it's not null
432            if ( wrapped != null )
433            {
434                out.writeInt( wrapped.length );
435                
436                if ( wrapped.length > 0 )
437                {
438                    out.write( wrapped, 0, wrapped.length );
439                }
440            }
441            else
442            {
443                out.writeInt( -1 );
444            }
445            
446            // Write the isNormalized flag
447            if ( normalized )
448            {
449                out.writeBoolean( true );
450                
451                // Write the normalized value, if not null
452                if ( normalizedValue != null )
453                {
454                    out.writeInt( normalizedValue.length );
455                    
456                    if ( normalizedValue.length > 0 )
457                    {
458                        out.write( normalizedValue, 0, normalizedValue.length );
459                    }
460                }
461                else
462                {
463                    out.writeInt( -1 );
464                }
465            }
466            else
467            {
468                out.writeBoolean( false );
469            }
470        }
471        
472        
473        /**
474         * Dumps binary in hex with label.
475         *
476         * @see Object#toString()
477         */
478        public String toString()
479        {
480            if ( wrapped == null )
481            {
482                return "null";
483            }
484            else if ( wrapped.length > 16 )
485            {
486                // Just dump the first 16 bytes...
487                byte[] copy = new byte[16];
488                
489                System.arraycopy( wrapped, 0, copy, 0, 16 );
490                
491                return "'" + StringTools.dumpBytes( copy ) + "...'";
492            }
493            else
494            {
495                return "'" + StringTools.dumpBytes( wrapped ) + "'";
496            }
497        }
498    }